Skip to main content

Request Management API

Not in Epic 1

This API is NOT part of Epic 1 - TTS Submission and Get Link.

Epic 1 uses the simplified TTS Submission API (/api/v1/text-to-speech) for direct audio generation.

This Request Management API describes a manual admin-driven workflow that has been deferred to future releases.

Epic 1 Flow: See Text-to-Speech Submission API instead.

Overview

The Request Management API handles all operations for text-to-audio requests including:

  • User request submission
  • Admin request viewing
  • Admin status updates
  • Admin audio upload
  • QR code generation

Base URL: /api/v1

Authentication:

  • User endpoints: JWT Bearer token (user role)
  • Admin endpoints: JWT Bearer token (admin role)

User Endpoints

Submit Request

Endpoint: POST /requests

Auth: Required (User)

Description: User submits text for audio generation

Request Body:

{
"text": "Welcome to MicDots! Scan the QR code...",
"voiceId": "voice-001"
}

Response (201 Created):

{
"success": true,
"data": {
"requestId": "REQ-123456789",
"status": "pending",
"text": "Welcome to MicDots! Scan the QR code...",
"voiceId": "voice-001",
"createdAt": "2024-01-22T14:30:00Z"
},
"message": "Request submitted successfully. You will receive a confirmation email shortly."
}

Side Effects:

  • Confirmation email sent to user
  • Request stored in database with status "pending"

Validation:

  • text: Required, 1-1000 characters
  • voiceId: Required, must be valid voice ID

Admin Endpoints

List All Requests

Endpoint: GET /admin/requests

Auth: Required (Admin)

Description: View all requests with filtering and pagination

Query Parameters:

{
status?: 'all' | 'pending' | 'processing' | 'done';
search?: string; // Search by request ID, user email, or text
page?: number; // Default: 1
limit?: number; // Default: 10, Max: 100
}

Response (200 OK):

{
"success": true,
"data": {
"requests": [
{
"id": "REQ-123456789",
"userId": "user-abc123",
"userEmail": "john@example.com",
"text": "Welcome to MicDots! Scan the QR code...",
"voiceId": "voice-001",
"voiceName": "Rachel",
"status": "pending",
"audioUrl": null,
"qrCodeUrl": null,
"playbackUrl": null,
"slug": null,
"createdAt": "2024-01-22T14:30:00Z",
"updatedAt": "2024-01-22T14:30:00Z"
}
],
"pagination": {
"currentPage": 1,
"totalPages": 3,
"totalRequests": 17,
"limit": 10
},
"statistics": {
"pending": 12,
"processing": 5,
"done": 143,
"total": 160
}
}
}

Get Request Details

Endpoint: GET /admin/requests/:id

Auth: Required (Admin)

Description: View detailed information for a single request

Response (200 OK):

{
"success": true,
"data": {
"id": "REQ-123456789",
"userId": "user-abc123",
"user": {
"email": "john@example.com",
"name": "John Doe"
},
"text": "Welcome to MicDots! Scan the QR code to hear your personalized message.",
"voiceId": "voice-001",
"voice": {
"name": "Rachel",
"gender": "female",
"accent": "american"
},
"status": "pending",
"audioUrl": null,
"qrCodeUrl": null,
"playbackUrl": null,
"slug": null,
"createdAt": "2024-01-22T14:30:00Z",
"updatedAt": "2024-01-22T14:30:00Z",
"statusHistory": [
{
"status": "pending",
"timestamp": "2024-01-22T14:30:00Z",
"changedBy": null
}
]
}
}

Error (404 Not Found):

{
"success": false,
"error": {
"code": "REQUEST_NOT_FOUND",
"message": "Request with ID 'REQ-123456789' not found."
}
}

Update Request Status

Endpoint: PATCH /admin/requests/:id/status

Auth: Required (Admin)

Description: Update request status (pending → processing)

Request Body:

{
"status": "processing"
}

Response (200 OK):

{
"success": true,
"data": {
"id": "REQ-123456789",
"status": "processing",
"updatedAt": "2024-01-22T15:00:00Z"
},
"message": "Status updated to 'processing'. Processing email sent to user."
}

Side Effects:

  • Status updated in database
  • Processing email sent to user
  • Status history logged

Validation:

  • Status transition must be valid (pending → processing, not done → pending)

File Upload Strategy: S3 Pre-Signed URLs

For audio file uploads, MicDots uses S3 pre-signed URLs to optimize performance and scalability:

Benefits:

  • Direct Upload: Frontend uploads directly to S3, bypassing backend
  • Faster: No backend bottleneck for large audio files
  • Scalable: Backend only handles metadata, not file transfers
  • Secure: Pre-signed URLs expire after 15 minutes
  • Progress Tracking: Frontend can track upload progress

Upload Flow:

  1. Admin requests pre-signed URL from backend
  2. Backend generates S3 pre-signed URL (15-minute expiration)
  3. Admin uploads audio file directly to S3 using pre-signed URL
  4. Admin submits S3 URL to backend for processing
  5. Backend generates QR code, updates status to "done", sends email

Get Audio Upload URL

Endpoint: POST /admin/requests/:id/upload-url

Auth: Required (Admin)

Description: Get pre-signed S3 URL for audio upload

Request Body:

{
"fileName": "request-audio.mp3",
"fileType": "audio/mpeg",
"fileSize": 5242880
}

Request Body Fields:

  • fileName (string, required) - Original file name
  • fileType (string, required) - MIME type (must be "audio/mpeg")
  • fileSize (number, required) - File size in bytes (max 10MB = 10485760 bytes)

Response (200 OK):

{
"success": true,
"data": {
"uploadUrl": "https://micdots-audio.s3.amazonaws.com/requests/REQ-123456789-1706025600.mp3?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...",
"audioUrl": "https://micdots-audio.s3.amazonaws.com/requests/REQ-123456789-1706025600.mp3",
"fileKey": "requests/REQ-123456789-1706025600.mp3",
"expiresIn": 900
},
"message": "Pre-signed upload URL generated. Valid for 15 minutes."
}

Response Fields:

  • uploadUrl (string) - Pre-signed S3 URL for uploading (valid 15 minutes)
  • audioUrl (string) - Final S3 URL for the uploaded file
  • fileKey (string) - S3 object key
  • expiresIn (number) - Expiration time in seconds (900 = 15 minutes)

Validation:

  • fileType must be audio/mpeg (MP3 only)
  • fileSize must be ≤ 10MB (10485760 bytes)
  • Request must exist and be in "pending" or "processing" status

Error (400 Bad Request):

{
"success": false,
"error": {
"code": "INVALID_FILE_TYPE",
"message": "Only MP3 files are supported. File type must be 'audio/mpeg'."
}
}

Error (413 Payload Too Large):

{
"success": false,
"error": {
"code": "FILE_TOO_LARGE",
"message": "Audio file must be less than 10MB (10485760 bytes)."
}
}

Upload Audio to S3 (Client-Side)

After getting the pre-signed URL, upload the file directly to S3 from the frontend:

Basic Upload (JavaScript/TypeScript):

async function uploadAudioToS3(uploadUrl: string, file: File): Promise<void> {
const response = await fetch(uploadUrl, {
method: 'PUT',
headers: {
'Content-Type': file.type
},
body: file
});

if (!response.ok) {
throw new Error(`S3 upload failed: ${response.status}`);
}
}

Upload with Progress Tracking:

function uploadAudioWithProgress(
uploadUrl: string,
file: File,
onProgress: (percent: number) => void
): Promise<void> {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();

// Track upload progress
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const percentComplete = (e.loaded / e.total) * 100;
onProgress(percentComplete);
}
});

// Handle completion
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
resolve();
} else {
reject(new Error(`S3 upload failed: ${xhr.status}`));
}
});

// Handle errors
xhr.addEventListener('error', () => {
reject(new Error('S3 upload failed'));
});

// Upload file
xhr.open('PUT', uploadUrl);
xhr.setRequestHeader('Content-Type', file.type);
xhr.send(file);
});
}

// Usage example
const file = document.querySelector('input[type="file"]').files[0];
const requestId = 'REQ-123456789';

// Step 1: Get pre-signed URL
const urlResponse = await fetch(`/api/v1/admin/requests/${requestId}/upload-url`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${adminToken}`
},
body: JSON.stringify({
fileName: file.name,
fileType: file.type,
fileSize: file.size
})
});

const { data } = await urlResponse.json();

// Step 2: Upload to S3 with progress tracking
await uploadAudioWithProgress(data.uploadUrl, file, (percent) => {
console.log(`Upload progress: ${percent.toFixed(1)}%`);
// Update progress bar in UI
});

// Step 3: Complete the upload by submitting the audio URL
await fetch(`/api/v1/admin/requests/${requestId}/upload`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${adminToken}`
},
body: JSON.stringify({
audioUrl: data.audioUrl
})
});

Complete Audio Upload

Endpoint: POST /admin/requests/:id/upload

Auth: Required (Admin)

Description: Complete audio upload with S3 URL, auto-generate QR code, mark as done

Request Body:

{
"audioUrl": "https://micdots-audio.s3.amazonaws.com/requests/REQ-123456789-1706025600.mp3"
}

Request Body Fields:

  • audioUrl (string, required) - S3 URL of the uploaded audio file

Response (200 OK):

{
"success": true,
"data": {
"id": "REQ-123456789",
"status": "done",
"audioUrl": "https://micdots-audio.s3.amazonaws.com/requests/REQ-123456789-1706025600.mp3",
"qrCodeUrl": "https://micdots-qr.s3.amazonaws.com/REQ-123456789-1706025600.png",
"playbackUrl": "https://micdots.com/play/welcome-message-1706025600",
"slug": "welcome-message-1706025600",
"updatedAt": "2024-01-22T16:00:00Z"
},
"message": "Audio uploaded successfully. QR code generated and done email sent to user."
}

Processing Steps (Automatic):

  1. Verify audio file exists at provided S3 URL
  2. Generate unique slug
  3. Generate QR code (PNG, 500x500px) pointing to playback URL
  4. Upload QR code to S3
  5. Update request status to "done"
  6. Send done email with audio link + QR code

Validation:

  • audioUrl must be valid S3 URL
  • Audio file must exist at the provided URL
  • Request must be in "pending" or "processing" status

Error (400 Bad Request):

{
"success": false,
"error": {
"code": "INVALID_AUDIO_URL",
"message": "The provided audio URL is invalid or the file does not exist."
}
}

Error (409 Conflict):

{
"success": false,
"error": {
"code": "INVALID_STATUS",
"message": "Request is already marked as done."
}
}

Public Endpoints

Get Playback Data

Endpoint: GET /play/:slug

Auth: Not required (Public)

Description: Get audio data for public playback page

Response (200 OK):

{
"success": true,
"data": {
"slug": "welcome-message-1234567890",
"audioUrl": "https://s3.amazonaws.com/micdots-audio/REQ-123456789-1234567890.mp3",
"text": "Welcome to MicDots! Scan the QR code...",
"voiceName": "Rachel",
"createdAt": "2024-01-22T14:30:00Z"
}
}

Error (404 Not Found):

{
"success": false,
"error": {
"code": "PLAYBACK_NOT_FOUND",
"message": "Audio with slug 'welcome-message-1234567890' not found."
}
}

Implementation Example

Express.js Server

import express from 'express';
import { authenticate, requireAdmin } from './middleware/auth';
import { RequestController } from './controllers/RequestController';

const app = express();
const requestController = new RequestController();

// User endpoints
app.post('/api/v1/requests',
authenticate,
requestController.submitRequest
);

// Admin endpoints
app.get('/api/v1/admin/requests',
authenticate,
requireAdmin,
requestController.listRequests
);

app.get('/api/v1/admin/requests/:id',
authenticate,
requireAdmin,
requestController.getRequestDetails
);

app.patch('/api/v1/admin/requests/:id/status',
authenticate,
requireAdmin,
requestController.updateStatus
);

app.post('/api/v1/admin/requests/:id/upload-url',
authenticate,
requireAdmin,
requestController.getUploadUrl
);

app.post('/api/v1/admin/requests/:id/upload',
authenticate,
requireAdmin,
requestController.completeAudioUpload
);

// Public endpoints
app.get('/api/v1/play/:slug',
requestController.getPlaybackData
);

Database Schema

interface Request {
id: string; // REQ-{timestamp}-{random}
userId: string;
text: string; // 1-1000 chars
voiceId: string;
status: 'pending' | 'processing' | 'done';
audioUrl: string | null; // S3 URL
qrCodeUrl: string | null; // S3 URL
playbackUrl: string | null; // micdots.com/play/{slug}
slug: string | null; // URL-friendly identifier
createdAt: Date;
updatedAt: Date;
}

interface StatusHistory {
id: string;
requestId: string;
status: string;
changedBy: string | null; // Admin ID or null for system
timestamp: Date;
}

Error Codes

CodeHTTP StatusDescription
UNAUTHORIZED401Missing or invalid authentication token
FORBIDDEN403User does not have required permissions
REQUEST_NOT_FOUND404Request ID does not exist
PLAYBACK_NOT_FOUND404Slug does not exist
INVALID_STATUS_TRANSITION400Cannot change from current status to requested status
FILE_TOO_LARGE413Audio file exceeds 10MB limit
INVALID_FILE_FORMAT400File is not MP3 format
TEXT_TOO_LONG400Text exceeds 1000 characters
INVALID_VOICE_ID400Voice ID does not exist
EMAIL_SEND_FAILED500Email service unavailable (logged, doesn't block request)