Developer documentation
Use Bunch API keys to create short-lived meeting join links for your users. This guide covers setup, authentication, request and response formats, implementation examples, error handling, and production rollout guidance for web apps, mobile apps, internal tools, and backend services.
Base application URL
https://www.bunch.community
Meeting domain
https://meet.bunch.community
Short links (dashboard)
https://www.bunch.community/m/{code}
Primary integration endpoint
POST /api/jwt
Authentication
Bearer API key or x-api-key header
Integration overview
Your backend sends Bunch a room name and user details. Bunch validates the API key, issues a signed JWT, and returns a secure join URL that opens the branded Bunch Meet experience for that user.
- Create an API key from the Bunch dashboard.
- Store the raw key securely in your application backend or secrets manager.
- Call
POST /api/jwtwhenever you want to create a meeting join link. - Set
returnHostand/orreturnGuestso each role returns to the right page in your app after leave or end meeting (required for reliable iframe embeds). - Redirect the user to the returned
joinUrlor open it in your app shell / iframe.
Create an API key
In Bunch, go to Dashboard → API keys and create a key for the system or product surface that will request meeting links.
- Name the key after the integration that will use it, such as `mobile-app` or `crm-backend`.
- Copy the raw key immediately after creation. It is only shown once.
- Store the raw key in your secrets manager, environment variables, or backend config.
- Do not embed API keys directly in browser code or ship them in mobile apps.
Authentication
Bunch supports two authentication styles for the JWT endpoint. Use one of the following on every request:
Create a meeting JWT
Use the endpoint below whenever you want to generate a secure meeting join URL for an external user or an authenticated user in your own application.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| roomName | string | Yes | Meeting room slug. Must be between 1 and 200 characters. |
| userName | string | Yes | Display name shown inside the meeting UI. |
| userEmail | string | No | Optional participant email for context and downstream workflows. |
| userAvatar | string | No | Optional absolute URL for the participant avatar image. |
| moderator | boolean | No | Defaults to false. Set to true when the participant should join as moderator. |
| returnUrl | string | No | Legacy fallback return URL. Applied as returnHost when moderator is true, returnGuest when false. |
| returnHost | string | No | HTTPS URL for hosts/moderators after leave or end meeting. Added to joinUrl automatically when set in the request. |
| returnGuest | string | No | HTTPS URL for guests (non-moderators) after they leave. Use on guest link flows or set both returnHost and returnGuest when issuing two links. |
Example request
{
"roomName": "team-standup",
"userName": "Constance Oshafi",
"userEmail": "constance@example.com",
"userAvatar": "https://example.com/avatar.png",
"moderator": true,
"returnHost": "https://client.example.com/meetings/team-standup/done"
}Success response
{
"joinUrl": "https://meet.bunch.community/team-standup?jwt=eyJ...&returnHost=https%3A%2F%2Fclient.example.com%2Fdone&return=https%3A%2F%2Fclient.example.com%2Fdone",
"roomName": "team-standup",
"jwt": "eyJ...",
"expiresIn": 7200
}Use the joinUrl value exactly as returned. It already includes JWT and return URL query parameters when you pass returnHost, returnGuest, or returnUrl in the request body.
API vs dashboard links: POST /api/jwt returns a direct Meet URL with an embedded JWT. Dashboard host/guest link APIs return a short URL on www.bunch.community (see below). API-issued meetings skip the dashboard guest admission gate.
Short links and guest admission
When a logged-in host copies links from the Bunch dashboard, Bunch returns compact short URLs instead of long Meet URLs with JWT query strings. This makes links easier to share in chat, SMS, and email.
Short link format
https://www.bunch.community/m/Ab12Cd34
- Host short links mint a fresh moderator JWT on each visit and redirect to
meet.bunch.community. - Guest short links open the guest join page at
/join/guest?c=Ab12Cd34where the guest enters their display name. - Short links expire after 7 days. Host links can be regenerated from the dashboard at any time.
Dashboard link API responses
Session endpoints that create shareable links now return:
{
"joinUrl": "https://www.bunch.community/m/Ab12Cd34",
"shortUrl": "https://www.bunch.community/m/Ab12Cd34",
"expiresIn": 7200
}joinUrl and shortUrl are the same for dashboard flows. Copy either value for end users. Integrators using POST /api/jwt still receive the full Meet URL in joinUrl (no short link indirection).
Guest admission (dashboard meetings only)
Meetings created via the Bunch dashboard require host approval the first time each guest joins. After the host admits a guest once, that guest can rejoin with the same display name without waiting again (remembered in the browser). Meetings created via POST /api/jwt disable this gate — guests join directly with the JWT you issue.
- Guest opens the short link and submits their display name.
POST /api/meetings/join-requestcreates a pending request (public, no auth).- Host sees pending guests on the dashboard and admits or declines.
- On approval, the guest receives a Meet JWT and is redirected into the call.
// Guest requests to join (from /join/guest page)
POST https://www.bunch.community/api/meetings/join-request
{ "code": "Ab12Cd34", "displayName": "Alex" }
// Response when waiting for host
{ "status": "pending", "requestId": "clx..." }
// Poll until approved
GET https://www.bunch.community/api/meetings/join-request?requestId=clx...
{ "status": "approved", "joinUrl": "https://meet.bunch.community/..." }
// Host lists pending guests (session cookie)
GET /api/meetings/{meetingId}/join-requests
// Host admits or declines (session cookie)
POST /api/meetings/{meetingId}/join-requests/{requestId}
{ "action": "approve" } // or "reject"Live participants (session)
Logged-in hosts can query who is currently in an active meeting. This uses telemetry from the Meet client and updates when participants join or leave.
GET /api/meetings/{meetingId}/participants
Cookie: <bunch session cookie>
{
"meeting": { "id": "...", "roomName": "team-standup", "isActive": true },
"participants": [
{ "id": "...", "displayName": "Alex", "joinedAt": "2026-06-02T12:00:00.000Z" }
]
}Return URLs — integrator requirements
After a participant leaves or a host ends the meeting, Bunch Meet redirects to the URL you provide. Pass return fields in the API request body; Bunch adds them to joinUrl automatically. Do not strip query parameters before sending users to Meet.
Body parameters (all meeting link APIs)
| Parameter | Type | Used on | Description |
|---|---|---|---|
| returnHost | string (HTTPS) | Host / moderator links | Where hosts go after Leave or End meeting for all. Required for iframe embeds. |
| returnGuest | string (HTTPS) | Guest links | Where guests go after they leave. Issue a separate guest link per room. |
| returnUrl | string (HTTPS) | Any link API | Legacy alias: maps to returnHost when moderator is true, returnGuest when false. |
Endpoints that accept return fields
| Endpoint | Auth | Return fields in body |
|---|---|---|
| POST /api/jwt | API key | returnHost, returnGuest, returnUrl (+ moderator selects host vs guest) |
| POST /api/meetings/join | Bunch session cookie | returnHost, returnUrl |
| POST /api/meetings/guest-link | Bunch session cookie | returnGuest, returnUrl (+ roomName, guestDisplayName, title) |
| POST /api/meetings/{id}/host-link | Bunch session cookie | returnHost, returnUrl |
| POST /api/meetings/{id}/guest-link | Bunch session cookie | returnGuest, returnUrl (+ optional guestDisplayName) |
Join URL query parameters (set by Bunch)
When return fields are in the request body, the response joinUrl includes:
returnHost— used when the JWT hasmoderator: truereturnGuest— used when the JWT hasmoderator: falsereturn— legacy mirror of the role-specific URL (same destination)
Integrator checklist
- Create two links per room when you have both hosts and guests: one JWT with
moderator: true+returnHost, one withmoderator: false+returnGuest. - Redirect or embed the full
joinUrlfrom the API response — do not rebuild the URL without return params. - Use absolute HTTPS return URLs on your own domains (HTTP localhost allowed for dev only).
- For iframe embeds, set
returnHostand listen forbunch:meeting-endedon the parent page (see below). - Do not append
return=manually unless you control encoding; prefer API body fields.
Redirect users back after leaving a meeting
When a user clicks Leave meeting or a host clicks End meeting for all, Bunch Meet redirects them using a return URL stored when the meeting loaded. Use separate URLs for hosts and guests so each role lands on the right page in your product.
- Hosts / moderators: pass
returnHostin the JWT request body (or appendreturnHost=to the join URL). - Guests: pass
returnGueston guest-link endpoints or appendreturnGuest=to the guest join URL. - Legacy:
returnUrlin the API body maps toreturnHostorreturnGuestbased onmoderator. A plainreturn=query param still works for either role when role-specific params are omitted.
// Host link (moderator)
const hostRes = await fetch("https://www.bunch.community/api/jwt", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify({
roomName: "team-standup",
userName: "Host User",
moderator: true,
returnHost: "https://client.example.com/app/meetings/123/host-done",
}),
});
const { joinUrl: hostJoinUrl } = await hostRes.json();
// Guest link (non-moderator) — separate JWT + returnGuest
const guestRes = await fetch("https://www.bunch.community/api/jwt", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify({
roomName: "team-standup",
userName: "Guest",
moderator: false,
returnGuest: "https://client.example.com/app/meetings/123/thanks",
}),
});
const { joinUrl: guestJoinUrl } = await guestRes.json();Embedded meetings (iframe)
If you embed the joinUrl in an iframe, set returnHost on the host link so moderators return to your app when they end the call. The Meet client notifies the parent window via postMessage and also attempts window.top navigation when allowed.
<iframe
id="bunch-meet"
src={hostJoinUrl}
allow="camera; microphone; fullscreen; display-capture"
style="width: 100%; height: 100%; border: 0;"
/>
// Parent page — distinguish Leave vs End meeting for all
window.addEventListener("message", (event) => {
if (event.data?.type !== "bunch:meeting-ended") return;
const { returnUrl, source, action } = event.data;
const endAction = action || source; // "leave" | "end"
if (endAction === "end") {
// Host clicked End meeting for all — mark session ended in your backend
void markSessionEnded();
}
// Always navigate back (silent leave or post-end redirect)
if (typeof returnUrl === "string" && returnUrl.startsWith("https://")) {
window.location.assign(returnUrl);
}
});postMessage payload
{
"type": "bunch:meeting-ended",
"returnUrl": "https://client.example.com/doctor/done?action=end",
"source": "end",
"action": "end"
}| Field | Values | Meaning |
|---|---|---|
| source | leave | end | leave = user clicked Leave; end = host clicked End meeting for all (or conference ended for everyone) |
| action | leave | end | Same as source — provided for convenience |
| returnUrl | HTTPS URL | Your returnHost or returnGuest URL with ?action=leave or ?action=end appended |
Mobile WebView (return URL redirect)
Mobile apps embedding Bunch in a WebView do not receive postMessage. Instead, intercept the return URL when Meet redirects after leave or end. Bunch appends an action query parameter:
returnHost=https://example.com/doctor/done?action=leave— host or participant clicked LeavereturnHost=https://example.com/doctor/done?action=end— host clicked End meeting for all (all participants receiveaction=endon their role-specific return URL)- Same pattern applies to
returnGuestfor non-moderator links.
// React Native WebView — intercept return URL
<WebView
source={{ uri: joinUrl }}
onShouldStartLoadWithRequest={(req) => {
const url = new URL(req.url);
const action = url.searchParams.get("action");
if (url.origin === "https://client.example.com" && action) {
if (action === "end") markSessionEnded();
navigateAway(url.pathname);
return false;
}
return true;
}}
/>Session guest links (logged-in host)
When the host is logged into Bunch, use POST /api/meetings/guest-link with an optional returnGuest field. The response returns a short URL on www.bunch.community; return parameters are stored server-side and applied when the guest is admitted and redirected to Meet.
POST https://www.bunch.community/api/meetings/guest-link
Content-Type: application/json
Cookie: <bunch session cookie>
{
"roomName": "team-standup",
"guestDisplayName": "Alex",
"returnGuest": "https://client.example.com/meetings/team-standup/thanks"
}
// Response
{
"joinUrl": "https://www.bunch.community/m/Ab12Cd34",
"shortUrl": "https://www.bunch.community/m/Ab12Cd34",
"roomName": "team-standup",
"expiresIn": 7200
}- Use absolute HTTPS URLs (localhost HTTP is allowed for development only).
- Two links per room: issue one JWT with
moderator: true+returnHost, and one withmoderator: false+returnGuest. - Security: only set return URLs to domains you control; validate
postMessageorigins in production if needed.
cURL example
curl -X POST "https://www.bunch.community/api/jwt" \
-H "Authorization: Bearer YOUR_BUNCH_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"roomName": "team-standup",
"userName": "Constance Oshafi",
"userEmail": "constance@example.com",
"moderator": true,
"returnHost": "https://client.example.com/meetings/team-standup/done"
}'JavaScript example
const response = await fetch("https://www.bunch.community/api/jwt", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.BUNCH_API_KEY}`,
},
body: JSON.stringify({
roomName: "team-standup",
userName: "Constance Oshafi",
userEmail: "constance@example.com",
moderator: true,
returnHost: "https://client.example.com/meetings/team-standup/done",
}),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || "Failed to create Bunch meeting link");
}
// joinUrl already includes returnHost; redirect or embed it
return data.joinUrl;Error handling
| Status | Meaning | Typical cause | What to do |
|---|---|---|---|
| 400 | Bad Request | Missing fields, invalid email, invalid avatar URL, or malformed JSON. | Validate payloads before sending and log the response body during integration. |
| 401 | Unauthorized | Missing API key or invalid API key. | Confirm your key is present, active, and sent using Bearer auth or x-api-key. |
| 403 | Forbidden | Guest join request was declined by the host. | Ask the host to send a new guest link or admit the guest from the dashboard. |
| 410 | Gone | Meeting room was permanently closed. | Create a new room name; closed rooms cannot be reused. |
| 500 | Server misconfiguration | JWT secrets missing or mismatched between Bunch API and Meet infrastructure. | Ensure JWT_APP_ID and JWT_APP_SECRET match on bunch-web and bunch-meet-prosody. Retry later if the error persists. |
Session-based web app flow
If your users are logged into Bunch in the browser, use session cookies instead of an API key:
POST /api/meetings/join POST /api/meetings/guest-link
// Host join (session) — returns short URL
POST /api/meetings/join
{ "roomName": "team-standup", "title": "Standup", "returnHost": "https://www.bunch.community/dashboard" }
// { "joinUrl": "https://www.bunch.community/m/...", "shortUrl": "...", "expiresIn": 7200 }
// Guest share link (session) — short URL + host admission on first join
POST /api/meetings/guest-link
{
"roomName": "team-standup",
"guestDisplayName": "Alex",
"returnGuest": "https://client.example.com/thanks"
}Recommended production practices
- Generate join links from your backend, not from public frontend code.
- Rotate API keys when team ownership changes or if a key is exposed.
- Log request failures with response status and body for troubleshooting.
- Treat the returned JWT and join URL as sensitive, short-lived credentials.
- Use room naming conventions that map cleanly to your own meetings or records.
- Always pass
returnHost/returnGuest(orreturnUrl) when users must return to your app after a call.
Troubleshooting join failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Jitsi username/password login | User opened meet.bunch.community/room without ?jwt= in the URL. | Always use the full joinUrl from the API or a valid short link — never send users to a bare room path. |
| Token authentication failed | JWT_APP_SECRET mismatch between bunch-web and bunch-meet-prosody, or expired JWT (2h). | Verify Fly secrets match on all Meet apps; regenerate links if older than expiresIn. |
| Not allowed to join this call | roomName in URL does not match the room claim in the JWT (case or slug). | Use roomName exactly as returned by the API; Bunch normalizes to lowercase slugs. |
| Guest stuck on waiting screen | Host has not admitted the guest yet (dashboard meetings only). | Host opens dashboard → active meeting → Admit. API meetings skip this step. |
Frequently asked questions
How do I tell Leave apart from End meeting for all?
Check source or action on the bunch:meeting-ended postMessage (web iframe), or read the action query param on the return URL redirect (mobile WebView). Use end to mark a clinical session as formally ended; use leave when the user only stepped out of the call.
Do API integrations use short links?
No. POST /api/jwt returns a direct meet.bunch.community URL with JWT and return query parameters. Short links and guest admission apply to dashboard-created meetings only.
How long are Bunch JWTs valid?
The current integration response returns expiresIn: 7200, which is a 2-hour lifetime.
Can I embed the returned meeting link?
Yes. Load the joinUrl in an iframe or WebView. For hosts ending a call from an iframe, pass returnHost when creating the link and listen for postMessage events with type bunch:meeting-ended on the parent page. See Redirect users back after leaving a meeting above.
Can I create separate keys per integration?
Yes. That is the recommended approach so you can rotate or revoke one integration without affecting others.
Which return field should I use?
Prefer returnHost for moderators and returnGuest for guests. Use returnUrl only if you have a single integration path and set moderator per call. All three are optional but strongly recommended for production embeds and multi-page apps.
Start integrating Bunch
Create an API key, connect your backend, and start returning branded meeting links from your own product flows.