onepizza.io API

A RESTful + WebSocket API for creating and managing video meetings. Built for developers, agents, and teams.

Version: 1.2.0 Last updated: May 5, 2026 Base URL: https://onepizza.io Interactive API Reference ↗ openapi.yaml ↗
Three ways to use the API:
REST — full programmatic control. Try it live in the Interactive API Reference, or import /openapi.yaml into Postman / Insomnia / your favourite client generator.
Socket.IO — real-time events for participants and bots (see section 10).
MCP server — drop-in integration for Claude, Cursor, and other AI agents (see section 25).
Authentication overview:
REST API calls — API key in x-api-key header (obtain from Dashboard → API Keys)
Admin meeting actionsx-admin-token header (returned when meeting is created)
Dashboard / user account endpoints — session cookie (set on login, used automatically by browsers)
Guest meetings — no authentication required (rate-limited to 5/hour per IP)

Table of Contents

1. Account & Authentication 2. API Keys 3. Meetings (API) 4. Guest Meetings 5. Scheduled Meetings 6. Meeting Settings & Controls 7. Participant Controls 8. Meeting History 9. Webhooks 10. Real-time Events (Socket.IO) 11. Waiting Room Events 12. WebRTC Flow 13. ICE Server Config 14. Company Accounts 15. Billing 16. Errors & Rate Limits 17. Environment Variables 18. Admin Analytics API 19. Meeting Transcripts 20. Meeting Recordings 21. Health Endpoints 22. Quick Start 23. Keyboard Shortcuts 24. Features Overview 25. MCP Integration (AI agents) 26. SDKs & Client Generation

1. Account & Authentication

POST /api/auth/register

Create a new account. Returns the account API key. A welcome email is sent if RESEND_API_KEY is configured.

FieldTypeRequiredDescription
emailstringYesEmail address (must be unique)
passwordstringYesMinimum 8 characters
accountTypestringNo"personal" (default) or "company"
companyNamestringIf companyRequired when accountType is "company"
curl -X POST /api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"secret123"}'

# Response
{
  "message": "Account created",
  "email": "you@example.com",
  "apiKey": "mk_abc123...",
  "accountType": "personal"
}
POST /api/auth/login

Log in to your account. Sets a session cookie used by the dashboard.

FieldTypeDescription
emailstringAccount email
passwordstringAccount password
POST /api/auth/logout

Destroy the current session and clear the session cookie.

GET /api/auth/me session

Get the current authenticated user's profile. Used by the dashboard to verify login state.

{
  "id": 42,
  "email": "you@example.com",
  "accountType": "personal",
  "balance": "25.0000",
  "company_id": null,
  "is_admin": false,
  "created_at": "2026-03-01T12:00:00Z"
}
POST /api/auth/forgot-password

Request a password reset email. Rate-limited to 3 requests per 15 minutes per IP. Silently succeeds even if email not found (prevents user enumeration).

FieldTypeDescription
emailstringThe email address to send the reset link to
GET /api/auth/reset-password

Validate a password reset token. Called client-side to check if the token from the email link is still valid (tokens expire after 1 hour).

Query ParamDescription
tokenThe reset token from the email link
POST /api/auth/reset-password

Set a new password using a valid reset token.

FieldTypeDescription
tokenstringReset token from the email link
passwordstringNew password (min 8 characters)
POST /api/auth/change-password session

Change the current user's password. Requires authentication. Sends a "password changed" security email on success.

FieldTypeDescription
currentPasswordstringCurrent account password
newPasswordstringNew password (min 8 characters)

2. API Keys

API keys authenticate REST API calls via the x-api-key header. Multiple keys can be active simultaneously. Keys are prefixed mk_.

GET /api/user/keys session

List all API keys for the current user.

{
  "keys": [
    { "id": 1, "key": "mk_abc123...", "label": "Production", "is_active": true, "created_at": "2026-03-01T12:00:00Z" }
  ]
}
POST /api/user/keys session

Generate a new API key.

FieldTypeDescription
labelstringOptional label for this key (e.g. "Production")
DELETE /api/user/keys/:id session

Revoke and permanently delete an API key. Any requests using the deleted key will immediately return 401.

3. Meetings (API)

POST /api/meetings

Create a new meeting (instant or scheduled). Requires an active API key. Credits are deducted when the meeting ends based on duration and peak participant count.

FieldTypeRequiredDescription
titlestringNoMeeting title (default: "Untitled Meeting")
scheduledAtstringNoISO 8601 UTC date to schedule the meeting for a future time. If omitted, the meeting is immediately active.
muteOnJoinbooleanNoAuto-mute new participants (default: false)
videoOffOnJoinbooleanNoAuto-disable video for new participants (default: false)
maxParticipantsnumberNoMaximum concurrent participants (default: 50)
# Instant meeting
curl -X POST /api/meetings \
  -H "Content-Type: application/json" \
  -H "x-api-key: mk_yourkey" \
  -d '{"title": "Team Standup"}'

# Response
{
  "meetingId": "abc-defg-hij",
  "adminToken": "d4e5f6...",
  "joinUrl": "/join/abc-defg-hij",
  "title": "Team Standup",
  "status": "active",
  "settings": { "muteOnJoin": false, "videoOffOnJoin": false, "maxParticipants": 50, "locked": false, "waitingRoom": false }
}

# Scheduled meeting
curl -X POST /api/meetings \
  -H "Content-Type: application/json" \
  -H "x-api-key: mk_yourkey" \
  -d '{"title":"Weekly Sync","scheduledAt":"2026-04-01T14:00:00Z"}'

# Response
{
  "meetingId": "xyz-abcd-efg",
  "adminToken": "a1b2c3...",
  "joinUrl": "/join/xyz-abcd-efg",
  "title": "Weekly Sync",
  "scheduledAt": "2026-04-01T14:00:00.000Z",
  "status": "scheduled",
  "settings": { "muteOnJoin": false, "videoOffOnJoin": false, "maxParticipants": 50, "locked": false, "waitingRoom": false }
}
GET /api/meetings

List all currently active and scheduled meetings for the authenticated API key's user.

{
  "meetings": [
    { "meetingId": "abc-defg-hij", "title": "Team Standup", "status": "active", "participantCount": 3, "createdAt": 1710000000000 },
    { "meetingId": "xyz-abcd-efg", "title": "Weekly Sync", "status": "scheduled", "scheduledAt": "2026-04-01T14:00:00.000Z", "participantCount": 0 }
  ]
}
GET /api/meetings/:meetingId

Get details of a specific meeting including current participants and settings.

{
  "meetingId": "abc-defg-hij",
  "title": "Team Standup",
  "status": "active",
  "createdAt": 1710000000000,
  "participantCount": 2,
  "participants": [
    { "participantId": "a1b2c3", "name": "Alice", "isMuted": false, "isVideoOff": false, "isScreenSharing": false, "joinedAt": 1710000001000 }
  ],
  "settings": { "muteOnJoin": false, "videoOffOnJoin": false, "maxParticipants": 50, "locked": false, "waitingRoom": false }
}
DELETE /api/meetings/:meetingId admin token

End a meeting, disconnect all participants, and trigger billing. Requires x-admin-token.

curl -X DELETE /api/meetings/abc-defg-hij \
  -H "x-api-key: mk_yourkey" \
  -H "x-admin-token: d4e5f6..."

4. Guest Meetings

Guest meetings are created without authentication. They are in-memory only (no billing, no persistence) and rate-limited to prevent abuse.

POST /api/meetings/guest

Create a guest meeting with no API key. Limited to 5 meetings per hour per IP address. This endpoint powers the homepage "Start a meeting" button.

FieldTypeDescription
titlestringOptional meeting title (default: "Quick Meeting")
curl -X POST /api/meetings/guest \
  -H "Content-Type: application/json" \
  -d '{"title": "Quick Sync"}'

# Response
{
  "meetingId": "qwerty-abc",
  "adminToken": "uuid...",
  "joinUrl": "/join/qwerty-abc",
  "title": "Quick Sync"
}
Note: Guest meetings are not persisted, not billed, and are lost when the server restarts. For production use, create a real account and use POST /api/meetings.

5. Scheduled Meetings

How scheduling works:
• Pass scheduledAt (ISO 8601 UTC) to POST /api/meetings
• Meeting stays in scheduled status until the scheduled time arrives
• At the scheduled time the meeting auto-activates — participants can join
• If someone tries to join early, they get an error with the scheduled time
• Cancel a scheduled meeting with DELETE /api/meetings/:meetingId
GET /api/meetings/scheduled/list

List all scheduled (not yet started) meetings for the authenticated API key's user.

curl /api/meetings/scheduled/list -H "x-api-key: mk_yourkey"

# Response
{
  "meetings": [
    { "meetingId": "xyz-abcd-efg", "title": "Weekly Sync", "scheduledAt": "2026-04-01T14:00:00.000Z", "status": "scheduled", "createdAt": 1710000000000 }
  ]
}

6. Meeting Settings & Controls

All settings and lock/unlock endpoints require the x-admin-token header.

PATCH /api/meetings/:meetingId/settings admin token

Update meeting settings live. Changes are broadcast to all participants via the meeting:settings-updated Socket.IO event.

FieldTypeDescription
titlestringRename the meeting
muteOnJoinbooleanAuto-mute new joiners
videoOffOnJoinbooleanAuto-disable video for new joiners
maxParticipantsnumberChange the participant cap
lockedbooleanLock or unlock the meeting
waitingRoombooleanEnable or disable the waiting room
POST /api/meetings/:meetingId/lock admin token

Lock the meeting — no new participants can join until unlocked.

POST /api/meetings/:meetingId/unlock admin token

Unlock the meeting, allowing participants to join again.

POST /api/meetings/:meetingId/invite admin token

Generate a one-use invite link for a new participant.

FieldTypeDescription
namestringOptional — pre-fills the participant's name on the join page
{
  "joinUrl": "/join/abc-defg-hij?invite=x1y2z3&name=Bob",
  "inviteToken": "x1y2z3"
}

7. Participant Controls

All endpoints require both x-api-key and x-admin-token. Actions are applied immediately via Socket.IO to the target participant.

POST /api/meetings/:meetingId/participants/:participantId/mute admin token

Force-mute a specific participant. The participant receives an admin:mute event and their audio is disabled client-side.

POST /api/meetings/:meetingId/participants/:participantId/unmute admin token

Send an unmute request to a specific participant.

POST /api/meetings/:meetingId/participants/:participantId/kick admin token

Remove a participant immediately. They receive an admin:kick event with a reason.

FieldTypeDescription
reasonstringOptional message shown to the removed participant
POST /api/meetings/:meetingId/mute-all admin token

Mute every participant simultaneously. Broadcasts meeting:all-muted to the room.

8. Meeting History

GET /api/meetings/history session

Get the meeting usage history for the authenticated user (or their company). Includes duration, peak participants, and credit cost per meeting.

curl /api/meetings/history -b session_cookie

# Response
{
  "meetings": [
    {
      "id": 1,
      "meeting_id": "abc-defg-hij",
      "title": "Team Standup",
      "started_at": "2026-03-19T10:00:00Z",
      "ended_at": "2026-03-19T10:30:00Z",
      "duration_minutes": "30.00",
      "peak_participants": 4,
      "cost_usd": "0.0120"
    }
  ]
}

9. Webhooks

Register HTTPS endpoints to receive real-time notifications about meeting events. Webhooks are sent as POST requests with a JSON body.

GET /api/webhooks session

List all configured webhook endpoints for the current user.

{
  "webhooks": [
    { "id": 1, "url": "https://your-app.com/webhook", "events": ["meeting.ended","participant.left"], "created_at": "2026-03-01T12:00:00Z" }
  ]
}
POST /api/webhooks session

Register a new webhook endpoint.

FieldTypeDescription
urlstringHTTPS URL to receive webhook events
eventsstring[]Array of event names to subscribe to (see below)

Available Events

EventDescription
meeting.endedFired when a meeting ends (via DELETE or all participants leave)
participant.joinedFired when a participant joins the meeting
participant.leftFired when a participant disconnects

Webhook Payload

{
  "event": "meeting.ended",
  "meetingId": "abc-defg-hij",
  "timestamp": 1710000000000,
  "data": {
    "title": "Team Standup",
    "durationMinutes": 30,
    "peakParticipants": 4,
    "costUsd": "0.0120"
  }
}
DELETE /api/webhooks/:id session

Remove a webhook endpoint. Events will no longer be sent to that URL.

10. Real-time Events (Socket.IO)

Connect to the Socket.IO server at the base URL. The same server handles both signaling and meeting state.

import { io } from "socket.io-client";
const socket = io("https://onepizza.io");

Client → Server

EventPayloadDescription
join-meeting{ meetingId, name, isAdmin, adminToken }Join a meeting. Pass isAdmin: true and the adminToken to join as host.
signal:offer{ to: participantId, offer }Send a WebRTC offer to a specific participant
signal:answer{ to: participantId, answer }Send a WebRTC answer
signal:ice-candidate{ to: participantId, candidate }Send an ICE candidate
media:toggle-audio{ isMuted: boolean }Broadcast your mute state to all participants
media:toggle-video{ isVideoOff: boolean }Broadcast your video state
media:screen-share{ isScreenSharing: boolean }Broadcast screen-share state
raise-hand{ isHandRaised: boolean }Raise or lower your hand
react{ emoji: string }Send a floating emoji reaction (👍 ❤️ 😂 🎉 👏)
recording:broadcast-started{ hostName: string }Notify participants that the host has started recording
recording:broadcast-stopped{}Notify participants that the host has stopped recording
chat:message{ text: string, replyTo?: string }Send a chat message (max 2000 chars). Optional replyTo is the message ID to reply to.
chat:react{ msgId: string, emoji: string }React to a chat message with an emoji
captions:update{ pid: participantId, text: string, final: boolean }Send live caption text from speech recognition (interim or final result)

Server → Client

EventPayloadDescription
joined{ participantId, participants[], settings, title, isAdmin, muteOnJoin, videoOffOnJoin, isRecording, recordingHostName }Confirmed join — includes all current participants, meeting settings, and active recording state
participant:joined{ participantId, name, isMuted, isVideoOff, isAdmin }A new participant joined the room
participant:left{ participantId, name }A participant disconnected
participant:updated{ participantId, isMuted?, isVideoOff?, isScreenSharing?, isHandRaised? }A participant's media or hand state changed
signal:offer{ from: participantId, offer }Incoming WebRTC offer
signal:answer{ from: participantId, answer }Incoming WebRTC answer
signal:ice-candidate{ from: participantId, candidate }Incoming ICE candidate
admin:mute{}You were force-muted by the host
admin:unmute{}The host sent you an unmute request
admin:kick{ reason: string }You were removed from the meeting
meeting:ended{ reason: string }The meeting was ended by the host or admin
meeting:all-muted{}All participants were muted
meeting:settings-updated{ muteOnJoin, videoOffOnJoin, maxParticipants, locked, waitingRoom }Meeting settings changed
chat:message{ from: participantId, name, text, timestamp, replyTo: string|null }A chat message was sent in the room. replyTo is null or the ID of the message being replied to.
react{ participantId, emoji }An emoji reaction from a participant
recording:started{ hostName: string }The host has started recording — display consent notice
recording:stopped{}Recording has ended — dismiss consent notice
chat:react{ msgId: string, participantId, emoji: string }A participant reacted to a chat message
captions:update{ pid: participantId, text: string, final: boolean }Live caption text from a participant's speech recognition
error{ message: string }Something went wrong (meeting not found, locked, full, or scheduled)
Chat rich text: Chat messages support markdown-lite formatting: **bold**, *italic*, `code`, ```code blocks```.

Breakout Room Events (Client → Server)

EventPayloadDescription
breakout:create{ rooms: string[] }Admin creates breakout rooms (array of room names, max 20)
breakout:assign{ assignments: [{participantId, roomId}] }Admin assigns participants to rooms
breakout:join{ roomId }Participant joins assigned breakout room
breakout:leaveParticipant returns to main room
breakout:broadcast{ message }Admin sends message to all breakout rooms
breakout:closeAdmin closes all rooms, returns everyone to main

Breakout Room Events (Server → Client)

EventPayloadDescription
breakout:created{ rooms: [{id, name, participantCount}] }Rooms created successfully
breakout:updated{ rooms: [{id, name, participantCount, participants}] }Room participant list updated
breakout:assigned{ roomId, roomName }You've been assigned to a room
breakout:participant-joined{ participantId, roomId }Someone joined your breakout room
breakout:participant-left{ participantId }Someone left your breakout room
breakout:message{ from, text, timestamp }Broadcast message from host
breakout:closedYour breakout room is closing
breakout:all-closedAll breakout rooms closed

Live Stream Events (Client → Server)

EventPayloadDescription
stream:start{ url }Admin starts live broadcast (RTMP URL)
stream:stopAdmin stops live broadcast

Live Stream Events (Server → Client)

EventPayloadDescription
stream:started{ hostName }Live broadcast started
stream:stoppedLive broadcast ended

11. Waiting Room

When waiting room is enabled for a meeting (via settings), participants queue up before being admitted by the host.

Enable the waiting room via PATCH /api/meetings/:meetingId/settings with { "waitingRoom": true }.

Waiting Room Events (Client → Server)

EventPayloadDescription
waiting-room:join{ meetingId, name }Join the waiting queue (called automatically instead of join-meeting when waiting room is active)
waiting-room:admit{ meetingId, socketId }Host only — admit a waiting participant into the meeting
waiting-room:deny{ meetingId, socketId }Host only — deny and remove a participant from the queue

Waiting Room Events (Server → Client)

EventPayloadDescription
waiting-room:waiting{ message }Sent to the waiting participant — confirms they are in the queue
waiting-room:admitted{}You were admitted — proceed to join-meeting
waiting-room:denied{ message }The host declined your entry
waiting-room:participant-waiting{ socketId, name, count, removed? }Sent to host(s) — a participant is waiting or was admitted/denied

12. WebRTC Flow

The service uses a full mesh topology — each participant connects directly to every other participant via WebRTC peer connections.

1. You emit join-meeting → server responds with "joined" (includes all current participants)
2. For each existing participant, you create an RTCPeerConnection and send them an offer via signal:offer
3. They receive your offer → create an answer → send via signal:answer
4. Both sides exchange ICE candidates via signal:ice-candidate
5. Media (audio/video) flows directly peer-to-peer

Supported media tracks:
  - Audio:       Microphone (mutable)
  - Video:       Camera (toggleable, device-selectable)
  - Screen:      Screen share (replaces camera track temporarily)
  - Background:  Canvas-based blur filter (processed stream replaces camera track)
Mesh topology note: Full mesh scales to roughly 6–8 participants before performance degrades on average hardware. For larger meetings, consider an SFU (Selective Forwarding Unit) architecture.

13. ICE Server Configuration

GET /api/config/ice-servers

Returns the STUN/TURN server list used for WebRTC. The web client calls this automatically on join. If TURN credentials are configured via environment variables, they are included here.

# Default response (STUN only)
{
  "iceServers": [
    { "urls": "stun:stun.l.google.com:19302" },
    { "urls": "stun:stun1.l.google.com:19302" }
  ]
}

# With TURN configured
{
  "iceServers": [
    { "urls": "stun:stun.l.google.com:19302" },
    { "urls": "turn:your-turn-server.com:3478", "username": "...", "credential": "..." }
  ]
}

14. Company Accounts

Company accounts let multiple users share a single credit balance and API keys under one workspace. Any credit deducted by a member comes from the company balance.

POST /api/auth/register

Create a company account by setting accountType: "company". An invite code is returned, which members use to join.

curl -X POST /api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"owner@acme.com","password":"secret123","accountType":"company","companyName":"Acme Corp"}'

# Response
{
  "message": "Account created",
  "email": "owner@acme.com",
  "apiKey": "mk_abc123...",
  "inviteCode": "aBcDeFgHiJkL",
  "accountType": "company"
}
POST /api/company/join session

Join an existing company workspace using an invite code. The user's balance merges into the company balance.

FieldTypeDescription
inviteCodestringThe invite code from the company owner
curl -X POST /api/company/join \
  -H "Content-Type: application/json" \
  -b session_cookie \
  -d '{"inviteCode": "aBcDeFgHiJkL"}'

# Response
{ "message": "Joined Acme Corp", "companyId": 1, "companyName": "Acme Corp" }
GET /api/company/members session

List all members of the current user's company. Only available to company account owners.

{
  "members": [
    { "id": 1, "email": "owner@acme.com", "role": "owner", "joined_at": "2026-03-01T12:00:00Z" },
    { "id": 2, "email": "dev@acme.com", "role": "member", "joined_at": "2026-03-05T08:00:00Z" }
  ]
}
DELETE /api/company/members/:id session

Remove a member from the company. Only the company owner can do this.

15. Billing

Credits are consumed at meeting end based on duration × peak participants × the rate configured in the admin panel (default: $0.004/participant-minute). If a user belongs to a company, the company balance is used instead of the personal balance. A low-balance alert email is sent when balance drops below $2.00.

POST /api/billing/stripe/checkout session

Create a Stripe Checkout session to top up credits via credit/debit card. Redirects the user to Stripe's hosted checkout page.

FieldTypeDescription
amountUsdnumberAmount in USD to top up (min $5, max $1000)
curl -X POST /api/billing/stripe/checkout \
  -H "Content-Type: application/json" \
  -b session_cookie \
  -d '{"amountUsd": 20}'

# Response
{ "url": "https://checkout.stripe.com/c/pay/..." }
GET /api/billing/usdc/address session

Get the USDC (ERC-20) deposit address for your account. Each user/company gets a unique HD wallet address derived from the server's mnemonic. Send USDC on Ethereum mainnet to this address — the server polls for deposits and credits your balance automatically.

curl /api/billing/usdc/address -b session_cookie

# Response
{ "address": "0xABC123..." }
1 USDC = $1.00 USD credit. Minimum deposit: $5 USDC. Only Ethereum mainnet USDC (ERC-20) is supported.
GET /api/billing/history session

Get balance and transaction history for the current user or their company.

{
  "balance": "25.0000",
  "transactions": [
    { "id": 1, "amount_usd": "20.0000", "type": "stripe_topup", "description": "Stripe top-up $20", "created_at": "2026-03-10T12:00:00Z" },
    { "id": 2, "amount_usd": "-0.0120", "type": "meeting_charge", "description": "Meeting abc-defg-hij (30m, 4 participants)", "created_at": "2026-03-19T10:30:00Z" }
  ]
}
POST /api/billing/stripe/webhook

Stripe webhook receiver (internal use). Credits are added automatically when Stripe confirms a payment. Configured via STRIPE_WEBHOOK_SECRET.

16. Errors & Rate Limits

HTTP Error Responses

All errors return JSON: { "error": "<message>" }

StatusCause
400Validation error (missing field, bad format, e.g. scheduledAt must be in the future)
401Missing or invalid x-api-key, or not logged in
402Insufficient credits to start a meeting
403Missing or invalid x-admin-token
404Meeting, participant, or resource not found
409Conflict (email already in use, already in a company, etc.)
429Rate limit exceeded — slow down requests
500Internal server error

Rate Limits

Endpoint GroupLimit
Login / Register10 requests / 15 min per IP
Forgot / Reset password3 requests / 15 min per IP
Guest meeting creation5 requests / 1 hour per IP
API (authenticated)100 requests / 15 min per IP

17. Environment Variables

VariableRequiredDescription
DATABASE_PUBLIC_URLYesPostgreSQL connection string
SESSION_SECRETYesSession signing secret (min 32 chars, random)
ADMIN_EMAILYesAdmin account email (seeded on first start)
ADMIN_PASSWORDYesAdmin account password
APP_URLNoPublic base URL for email links (default: http://localhost:3000)
PORTNoHTTP server port (default: 3000)
NODE_ENVNodevelopment or production
ALLOWED_ORIGINSNoComma-separated CORS origins (default: http://localhost:3000)
RESEND_API_KEYNoResend API key for transactional emails (silently skipped if unset)
STRIPE_SECRET_KEYNoStripe secret key — disables card payments if unset
STRIPE_WEBHOOK_SECRETNoStripe webhook signing secret
STRIPE_PRICE_IDNoStripe Price ID for credit top-ups
TURN_URLSNoTURN server URL (e.g. turn:your-server.com:3478)
TURN_USERNAMENoTURN server username
TURN_CREDENTIALNoTURN server password
CRYPTO_MNEMONICNoBIP-39 mnemonic for HD wallet (USDC deposits) — 12-word phrase

18. Admin Analytics API

Server-side analytics endpoints for platform administrators. All endpoints require an active admin session (cookie-based).

GET /admin/api/analytics/features Admin

Feature usage breakdown for tracked events (screen share, recording, chat, reactions, etc.).

Query ParamTypeDefaultDescription
daysnumber30Lookback window in days
# Response
{
  "features": [
    { "feature": "screen_share", "total_uses": 142, "unique_users": 38, "active_days": 22 },
    { "feature": "recording", "total_uses": 67, "unique_users": 15, "active_days": 18 }
  ],
  "days": 30
}
GET /admin/api/analytics/errors Admin

Aggregated error log grouped by event type, route, and message.

Query ParamTypeDefaultDescription
daysnumber30Lookback window in days
# Response
{
  "errors": [
    { "event_type": "error.webrtc", "route": "/join", "message": "ICE connection failed", "count": 12, "last_seen": "2026-03-20T14:30:00Z" }
  ],
  "days": 30
}
GET /admin/api/analytics/realtime Admin

Live meeting state from server memory (no DB query). Shows all currently active meetings and aggregate counts.

# Response
{
  "activeMeetings": 3,
  "totalParticipants": 14,
  "activeRecordings": 1,
  "activeScreenShares": 2,
  "meetingList": [
    { "id": "abc-defg-hij", "title": "Team Standup", "participantCount": 5, "duration": 1200, "isRecording": true }
  ]
}
GET /admin/api/analytics/retention Admin

Week-over-week user retention for the last 12 weeks.

# Response
{
  "retention": [
    { "week": "2026-03-10", "users": 120, "retained": 84 },
    { "week": "2026-03-03", "users": 115, "retained": 79 }
  ]
}
GET /admin/api/analytics/health Admin

Server health metrics including memory usage, DB pool stats, and active meeting counts.

# Response
{
  "uptimeSeconds": 86400,
  "memoryMB": 128.5,
  "heapUsedMB": 95.2,
  "heapTotalMB": 140.0,
  "dbPoolTotal": 20,
  "dbPoolIdle": 15,
  "dbPoolWaiting": 0,
  "activeMeetings": 3,
  "scheduledMeetings": 1
}
GET /admin/api/analytics/peak-hours Admin

Meeting distribution by day-of-week and hour. Useful for capacity planning.

Query ParamTypeDefaultDescription
daysnumber90Lookback window in days
# Response
{
  "peakHours": [
    { "dow": 1, "hour": 10, "count": 45 },
    { "dow": 3, "hour": 14, "count": 38 }
  ],
  "days": 90
}

Tracked Event Types

The analytics system tracks the following event types. These are stored in the analytics_events table and queried by the endpoints above.

Event TypeTriggerMeta Fields
feature.screen_shareScreen share start/stop{ action: 'start'/'stop' }
feature.recordingRecording start/stop{ action: 'start'/'stop' }
feature.chatChat message sent{ length }
feature.chat_reactionChat message reaction{ emoji }
feature.reactionFloating emoji reaction{ emoji }
feature.hand_raiseHand raise toggled{ raised: true/false }
feature.captionsCaptions used{}
feature.waiting_roomWaiting room admit/deny{ action: 'admit'/'deny' }
meeting.participant_joinedParticipant joins meeting{ meetingId, participantCount }
meeting.createdMeeting created{ scheduled, muteOnJoin, waitingRoom }
meeting.endedMeeting ended{ durationMinutes, peakParticipants, costUsd }

19. Meeting Transcripts

Chat messages are persisted to the database during meetings. You can retrieve them as JSON or download as a text file after the meeting.

GET /api/meetings/:meetingId/transcript

Get the full chat transcript for a meeting as JSON.

curl https://onepizza.io/api/meetings/abc-defg-hij/transcript \
  -H "x-api-key: YOUR_KEY"

# Response:
{
  "messages": [
    { "participant_name": "Alice", "text": "Hello everyone!", "created_at": "2026-03-22T14:30:00Z" },
    { "participant_name": "Bob", "text": "Hi Alice!", "created_at": "2026-03-22T14:30:05Z" }
  ]
}
GET /api/meetings/:meetingId/transcript/download

Download the chat transcript as a plain text file. Returns Content-Disposition: attachment.

curl -O https://onepizza.io/api/meetings/abc-defg-hij/transcript/download \
  -H "x-api-key: YOUR_KEY"

# Downloaded file format:
# [14:30:00] Alice: Hello everyone!
# [14:30:05] Bob: Hi Alice!

20. Meeting Recordings

Upload browser-recorded meeting files to the server for storage and later retrieval.

POST /api/meetings/:meetingId/recordings

Upload a recording file (multipart/form-data). Max file size: 500 MB. Accepts video/* and audio/* MIME types.

FieldTypeRequiredDescription
recordingFileREQUIREDThe recording file (video/webm, etc.)
curl -X POST https://onepizza.io/api/meetings/abc-defg-hij/recordings \
  -H "x-api-key: YOUR_KEY" \
  -F "recording=@meeting-recording.webm"

# Response (201):
{
  "id": 1,
  "filename": "meeting-recording.webm",
  "size_bytes": 15728640,
  "created_at": "2026-03-22T15:00:00Z"
}
GET /api/meetings/:meetingId/recordings

List all recordings for a meeting.

curl https://onepizza.io/api/meetings/abc-defg-hij/recordings \
  -H "x-api-key: YOUR_KEY"

# Response:
{
  "recordings": [
    { "id": 1, "filename": "meeting-recording.webm", "size_bytes": 15728640, "created_at": "2026-03-22T15:00:00Z" }
  ]
}
GET /api/recordings/:id/download

Download a recording file by ID. Returns the file with Content-Disposition: attachment.

curl -O https://onepizza.io/api/recordings/1/download \
  -H "x-api-key: YOUR_KEY"

21. Health Endpoints

Health check endpoints for monitoring and orchestration (Kubernetes, Railway, etc.). No authentication required.

GET /health

Full health check — verifies database connectivity.

// 200 OK
{ "status": "ok", "db": "connected" }

// 503 Service Unavailable
{ "status": "error", "db": "unavailable" }
GET /health/liveness

Liveness probe — always returns 200 if the process is running. Use for Kubernetes liveness checks.

// 200 OK
{ "status": "alive" }
GET /health/readiness

Readiness probe — returns 200 only if the database is reachable. Use for Kubernetes readiness checks and load balancer health.

// 200 OK
{ "status": "ready", "db": "connected" }

// 503 Service Unavailable
{ "status": "not ready", "db": "unavailable" }

22. Quick Start

1. Start a guest meeting (no account needed)

curl -X POST https://onepizza.io/api/meetings/guest \
  -H "Content-Type: application/json" \
  -d '{"title": "Quick Sync"}'
# Open the joinUrl in a browser to join

2. Create an account and get an API key

curl -X POST https://onepizza.io/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","password":"secret123"}'
# Save the apiKey from the response

3. Create, manage, and end a meeting via API

API_KEY="mk_yourkey"
BASE="https://onepizza.io"

# Create
RESP=$(curl -s -X POST $BASE/api/meetings \
  -H "Content-Type: application/json" \
  -H "x-api-key: $API_KEY" \
  -d '{"title":"Team Standup"}')

MEETING_ID=$(echo $RESP | jq -r '.meetingId')
ADMIN_TOKEN=$(echo $RESP | jq -r '.adminToken')
echo "Join: $BASE/join/$MEETING_ID"

# Check participants
curl -s $BASE/api/meetings/$MEETING_ID -H "x-api-key: $API_KEY" | jq

# Mute a participant
curl -s -X POST $BASE/api/meetings/$MEETING_ID/participants/PARTICIPANT_ID/mute \
  -H "x-api-key: $API_KEY" \
  -H "x-admin-token: $ADMIN_TOKEN"

# End the meeting
curl -s -X DELETE $BASE/api/meetings/$MEETING_ID \
  -H "x-api-key: $API_KEY" \
  -H "x-admin-token: $ADMIN_TOKEN"

4. Schedule a future meeting

curl -X POST https://onepizza.io/api/meetings \
  -H "Content-Type: application/json" \
  -H "x-api-key: $API_KEY" \
  -d '{"title":"Daily Standup","scheduledAt":"2026-04-01T09:00:00Z"}'

5. Connect via Socket.IO (Node.js)

import { io } from "socket.io-client";

const socket = io("https://onepizza.io");

socket.emit("join-meeting", {
  meetingId: "abc-defg-hij",
  name: "Bot",
  isAdmin: false,
  adminToken: null,
});

socket.on("joined", ({ participantId, participants }) => {
  console.log("I am", participantId, "— others:", participants.map(p => p.name));
});

socket.on("participant:joined", p => console.log(p.name, "joined"));
socket.on("chat:message", ({ name, text }) => console.log(`${name}: ${text}`));

// Send a chat message
socket.emit("chat:message", { text: "Hello from the bot!" });

// Raise hand
socket.emit("raise-hand", { isHandRaised: true });

// Emoji reaction
socket.emit("react", { emoji: "👍" });

23. Keyboard Shortcuts

The meeting UI supports the following keyboard shortcuts. All shortcuts work when focus is not inside a text input or textarea.

KeyAction
MToggle microphone mute/unmute
SpacePush-to-talk (hold to temporarily unmute while muted)
VToggle camera on/off
SToggle screen sharing
HRaise or lower hand
RToggle recording (host only)
CToggle chat panel
BToggle background blur
PToggle participants panel
LToggle meeting lock (host only)
TToggle live captions
UToggle UI (hide/show meeting controls)
?Show keyboard shortcuts help dialog
Push-to-talk: Hold the Space bar while muted to temporarily unmute. Releasing the key re-mutes automatically. This does not interfere with the mute button.

24. Features Overview

CategoryFeatureDescription
VideoWebRTC video callsFull mesh peer-to-peer video; camera device selection
VideoScreen sharingShare entire screen or application window
VideoBackground blurCanvas-based blur filter on your camera feed
VideoSpotlight / pinPin any participant to the main view; others go to a filmstrip
AudioMicrophone controlMute/unmute with device selection
AudioSpeaking indicatorBlue highlight on the currently speaking tile
CollaborationIn-meeting chatReal-time text chat visible to all participants
CollaborationEmoji reactionsFloating emoji reactions (👍 ❤️ 😂 🎉 👏) visible to everyone
CollaborationRaise handSignal to the host you want to speak; badge shown on your tile
CollaborationRecording consentHost can broadcast recording notice; all participants are notified
Host controlsParticipant managementMute, unmute, kick individual participants
Host controlsMute allSilence every participant with one action
Host controlsMeeting lockPrevent new participants from joining
Host controlsWaiting roomQueue participants; host admits or denies each one
SchedulingScheduled meetingsPlan meetings for a future time; auto-activate at start time
APIREST APIFull programmatic meeting management via API key auth
APISocket.IO eventsReal-time state sync for custom clients and bots
APIWebhooksPush notifications to your server for meeting and participant events
APIMeeting historyPer-meeting usage log with duration, participants, and cost
AccountsPassword resetEmail-based reset flow with 1-hour expiry
AccountsCompany workspacesMulti-user accounts with shared credits and invite codes
BillingUsage-based creditsCharged at meeting end: duration × peak participants × rate/min
BillingStripe paymentsTop up credits via credit/debit card (Stripe Checkout)
BillingUSDC cryptoTop up credits with USDC (ERC-20) via dedicated HD wallet address
InfrastructureSTUN/TURN supportConfigurable TURN credentials for NAT traversal
InfrastructureRate limitingPer-IP limits on auth, guest meetings, and API endpoints
InfrastructureCompressiongzip/brotli response compression
InfrastructureSecurity headershelmet.js CSP, HSTS, and other security headers

25. MCP Integration (AI agents)

onepizza.io ships with a built-in Model Context Protocol server (mcp-server.js) that exposes meeting management as tools any MCP-compatible AI agent can call — Claude Desktop, Claude Code, Cursor, Continue.dev, and more.

Two transports:
stdio — run locally for desktop agents (npm run mcp)
HTTP / SSE — run on port 3100 for remote agents (npm run mcp:http)

Configuration

# .env or shell env
ONEPIZZA_API_URL=https://onepizza.io   # API base URL
ONEPIZZA_API_KEY=mk_...                # your account API key

Claude Desktop / Claude Code config

{
  "mcpServers": {
    "onepizza": {
      "command": "node",
      "args": ["/absolute/path/to/meetingservice/mcp-server.js"],
      "env": {
        "ONEPIZZA_API_URL": "https://onepizza.io",
        "ONEPIZZA_API_KEY": "mk_yourkey"
      }
    }
  }
}

Available tools

The MCP server exposes these tools. Each is a thin wrapper over the REST or Socket.IO API documented above — agents can chain them naturally to run a full meeting.

ToolPurposeUnderlying API
create_meetingCreate an instant or scheduled meetingPOST /api/meetings
list_meetingsList active & scheduled meetingsGET /api/meetings
get_meetingInspect a meeting (participants, settings)GET /api/meetings/{id}
end_meetingEnd a live meeting / cancel a scheduled oneDELETE /api/meetings/{id}
update_meeting_settingsToggle muteOnJoin, lock, max participants, titlePATCH /api/meetings/{id}/settings
mute_participant / unmute_participantMute / unmute one participantPOST /participants/{id}/{mute|unmute}
kick_participantRemove a participant (with reason)POST /participants/{id}/kick
mute_allMute every participant at oncePOST /mute-all
join_meetingConnect a bot via Socket.IO; buffers eventsSocket.IO
leave_meetingDisconnect a joined bot & flush buffered eventsSocket.IO
send_chat_messagePost chat from the botSocket.IO chat:message
send_reactionSend a 👍 ❤️ 😂 🎉 👏 reactionSocket.IO react
create_pollCreate a multiple-choice / yes-no / rating poll (admin bot)Socket.IO poll:create
get_poll_resultsRead live poll resultsGET /api/meetings/{id}/polls
get_questionsRead Q&A questions sorted by upvotesGET /api/meetings/{id}/questions
create_breakout_rooms / end_breakoutManage breakout rooms (admin bot)Socket.IO breakout:*
create_template / list_templatesReusable meeting setting presets/api/templates
create_recurring / list_recurringRRULE-based recurring meetings/api/recurring
get_meeting_notes / update_meeting_notesRead / write the shared notes pad/api/meetings/{id}/notes
get_attendancePull the attendance report/api/meetings/{id}/attendance

Resources

Resource URIDescription
meetings://activeDynamic, real-time list of every active meeting on the server

Example agent prompt

"Schedule a 30-minute team standup for tomorrow at 9 AM UTC, enable waiting room, then once it starts post a welcome message and pin the host."

An MCP-aware agent will pick this apart into create_meeting with scheduledAt and waitingRoom: true, then later join_meeting + send_chat_message. No custom integration code required.

26. SDKs & Client Generation

Because the API ships with a full OpenAPI 3.1 spec, you can generate a typed client in any language with one command. Examples:

TypeScript / JavaScript

npx openapi-typescript https://onepizza.io/openapi.yaml -o ./onepizza.d.ts
# or, runtime client:
npx @hey-api/openapi-ts -i https://onepizza.io/openapi.yaml -o ./client

Python

pip install openapi-python-client
openapi-python-client generate --url https://onepizza.io/openapi.yaml

Go

go install github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest
oapi-codegen -package onepizza https://onepizza.io/openapi.yaml > onepizza.go

Postman / Insomnia / Bruno

Use File → Import and paste https://onepizza.io/openapi.yaml. Every endpoint, parameter, and example response is included with the correct auth headers prefilled.

Bundled JavaScript SDK

A hand-written, dependency-free wrapper lives in /sdk. See sdk/README.md for the latest install & usage.

Tip: The interactive API reference includes a "Try it" button for every endpoint — great for exploring the API without writing code first.
onepizza.io — onepizza.io · Interactive Reference · openapi.yaml