REST API for VideoCircle. All endpoints are versioned under /api/v1.
- Base URL (local):
http://localhost:8000 - Base URL (demo): the deployed backend URL (e.g.
https://videocircle-backend.onrender.com) - Content type:
application/json. Request bodies are capped at 40 KB. - CORS: open to all origins.
Two independent schemes — see ARCHITECTURE.md for the full rationale.
| Scheme | Used by | How |
|---|---|---|
| App auth | /api/v1/users/* protected routes |
Authorization: Bearer <token> where <token> is the opaque 40-char session token returned by Google sign-in (POST /api/v1/users/google). Not a JWT. 7-day TTL. |
| LiveKit auth | /api/v1/meet/get-token |
None. The endpoint is public; it issues a LiveKit JWT for the browser to connect to LiveKit Cloud. |
Every route is rate-limited to 100 requests per 15 minutes, per IP. Each route has its own named limiter so caps can diverge later, but they share one config today. Standard RateLimit-* headers are returned; the legacy X-RateLimit-* headers are disabled. Over the limit returns 429.
Every error response has the shape:
{ "status": 400, "message": "Human-readable reason" }Validation failures (Zod) additionally include an issues array:
{
"status": 400,
"message": "Validation failed",
"issues": [{ "path": "body.accessToken", "message": "accessToken is required" }]
}Unknown routes return 404 via the notFoundHandler.
Public. Used for uptime checks. Takes no parameters.
200 OK
{ "ok": true }Public. Sign-in is Google-only — there is no username/password. The browser uses Google's OAuth popup flow (useGoogleLogin from @react-oauth/google) and posts the resulting access token here. The backend validates it with google-auth-library (getTokenInfo, asserting the token's audience equals GOOGLE_CLIENT_ID), fetches the profile from Google's /userinfo endpoint, upserts the user by their Google sub, and returns a fresh session token. Each sign-in rotates the token — one active session per user.
Request body
| Field | Type | Rules |
|---|---|---|
accessToken |
string | required, non-empty — the Google OAuth access token |
{ "accessToken": "ya29.a0Af…" }200 OK
{ "token": "9f2c…<40 hex chars>" }Errors — 401 "Invalid Google access token" if introspection fails; 401 "Google token was not issued for this app" on audience mismatch; 401 "Could not fetch Google profile" if the userinfo request fails; 401 "Google account email is not verified" if Google reports the email as unverified; 400 on validation failure.
Auth required (Bearer). Validates the current session token and echoes back the user's public profile. Expired tokens are cleared server-side and rejected.
200 OK
{ "message": "Valid", "user": { "name": "Ada Lovelace", "email": "ada@example.com" } }Errors — 401 if the token is missing, invalid, or expired.
Auth required (Bearer). Appends a meeting to the authenticated user's history. The history record is keyed by the caller's user id (resolved from the token), so the body only carries the code.
Request body
| Field | Type | Rules |
|---|---|---|
meetingCode |
string | required, non-empty (trimmed) |
{ "meetingCode": "stand-up-42" }201 Created
{ "message": "Added code to history" }Errors — 401 if unauthenticated; 400 on validation failure.
Auth required (Bearer). Returns every meeting logged for the current user.
200 OK — an array of meeting records:
[
{
"_id": "665f…",
"user_id": "664a…",
"meetingCode": "stand-up-42",
"date": "2026-06-02T10:15:00.000Z"
}
]
user_idholds the user's Mongo_idas a string (not an ObjectId ref).
Errors — 401 if unauthenticated.
Public. Issues a short-lived LiveKit access token so the browser can connect directly to LiveKit Cloud. There is no app-auth on this endpoint — meeting codes are the only access control.
Query parameters
| Param | Type | Rules |
|---|---|---|
room |
string | required, non-empty (trimmed) — the meeting code |
username |
string | required, non-empty (trimmed) — display name |
GET /api/v1/meet/get-token?room=stand-up-42&username=Ada
200 OK
{
"token": "<LiveKit JWT>",
"url": "wss://your-project.livekit.cloud"
}The JWT carries a 1-hour TTL and the grants roomJoin, canPublish, and canSubscribe (no roomCreate — LiveKit auto-creates the room on first join). The token identity is ${username}-${randomUUID} (the UUID suffix prevents two same-named callers from colliding into one LiveKit participant); name is the raw username, used for chat display.
url is the backend's LIVEKIT_URL. The frontend connects using its own REACT_APP_LIVEKIT_URL — both must point at the same project. See LIVEKIT_SETUP.md.
Errors — 400 if room or username is missing/empty.
| Method | Endpoint | Auth | Success |
|---|---|---|---|
GET |
/api/v1/healthz |
Public | 200 |
POST |
/api/v1/users/google |
Public | 200 |
GET |
/api/v1/users/verify |
Bearer | 200 |
POST |
/api/v1/users/add_to_activity |
Bearer | 201 |
GET |
/api/v1/users/get_all_activity |
Bearer | 200 |
GET |
/api/v1/meet/get-token |
Public | 200 |