Submit a new speech with a YouTube URL or audio file upload.
Authentication: Required (Bearer token via Supabase Auth)
Content-Type: application/json
Request Body:
{
"speech_url": "https://youtube.com/watch?v=..."
}Success Response (201):
{
"success": true,
"data": {
"id": "uuid",
"user_id": "uuid",
"speech_url": "https://youtube.com/watch?v=...",
"submitted_at": "2025-10-30T12:00:00Z",
"week_start_date": "2025-10-27"
}
}Validation Rules:
- URL must be a valid YouTube URL (youtube.com/watch?v= or youtu.be/)
- URL must not have been previously submitted by the same user
- User must be authenticated
Content-Type: multipart/form-data
Request Body:
audio_file: Audio file (File object)
Success Response (201):
{
"success": true,
"data": {
"id": "uuid",
"user_id": "uuid",
"speech_url": "https://[project-id].supabase.co/storage/v1/object/public/speech-audio/...",
"submitted_at": "2025-10-30T12:00:00Z",
"week_start_date": "2025-10-27"
}
}Validation Rules:
- File must be an audio file (audio/*)
- File size must be less than 50 MB
- File is uploaded to Supabase Storage bucket
speech-audio - User must be authenticated
- 401 Unauthorized: User not logged in
{
"error": "Unauthorized"
}- 400 Bad Request: Invalid YouTube URL or audio file
{
"error": "Invalid YouTube URL format. Please provide a valid YouTube link."
}{
"error": "Audio file must be less than 50 MB"
}{
"error": "Invalid file type. Please upload an audio file."
}- 409 Conflict: Duplicate recording
{
"error": "You have already submitted this recording"
}- 500 Internal Server Error: Failed to upload
{
"error": "Failed to upload audio file"
}Fetch the current leaderboard with weekly and all-time statistics.
Authentication: Not required (public endpoint)
Success Response (200):
{
"data": [
{
"name": "John Doe",
"place": 1,
"all_time_speeches": 15,
"weekly_speeches": 3,
"avatar_url": "https://..."
},
{
"name": "Jane Smith",
"place": 2,
"all_time_speeches": 12,
"weekly_speeches": 2,
"avatar_url": "https://..."
}
]
}Response Fields:
name: User's display nameplace: Current ranking (based on all_time_speeches)all_time_speeches: Total number of speeches ever submittedweekly_speeches: Number of speeches submitted this week (Monday-Sunday)avatar_url: User's profile picture from Google OAuth (optional)speech_urls: Array of URLs to speech recordings (YouTube or Supabase Storage)
Notes:
- Results are sorted by
all_time_speechesin descending order - Weekly count is based on the current week (starts Monday at 00:00:00)
- Users with no speeches are included with counts of 0
The leaderboard supports real-time updates using Supabase Realtime:
const supabase = createClient();
const channel = supabase
.channel('speeches-changes')
.on('postgres_changes',
{ event: '*', schema: 'public', table: 'speeches' },
() => {
// Refetch leaderboard data
}
)
.subscribe();Events triggered on:
- New speech submission (INSERT)
- Speech update (UPDATE)
- Speech deletion (DELETE)
- User clicks "Log In" button
- Redirected to Google OAuth consent screen
- After approval, redirected to
/auth/callback - Callback handler:
- Exchanges code for session
- Creates/updates user profile in
userstable - Redirects to home page
- Session persisted via HTTP-only cookies
- Sessions automatically refreshed by middleware
- Session duration: as configured in Supabase (default 1 hour with 24h refresh)
- Logout clears session cookies
All endpoints follow consistent error response format:
{
"error": "Error message description"
}Common HTTP status codes:
200: Success (GET)201: Created (POST)400: Bad Request (validation error)401: Unauthorized (authentication required)409: Conflict (duplicate resource)500: Internal Server Error (server-side issue)