This document covers all HTTP API endpoints available in the rizzy-bytes helpdesk system. The front-end should use these endpoints to interact with backend services.
| Service | Port | Host (Dev) | Host (Docker) | URL |
|---|---|---|---|---|
| Authentication Service | 3001 | localhost | authentication-service | http://localhost:3001 or http://authentication-service:3000 |
| Broker Service | 3002 | localhost | broker-service | http://localhost:3002 or http://broker-service:3000 |
| Mail Service | 3004 | localhost | mail-service | http://localhost:3004 or http://mail-service:3000 |
| Flowise Proxy | 4000 | localhost | flowise-proxy | http://localhost:4000 or http://flowise-proxy:4000 |
| Front-end | 3000 | localhost | front-end | http://localhost:3000 |
| Logger Service | 3005 | localhost | logger-service | http://localhost:3005 or http://logger-service:3000 |
| Prometheus (metrics API) | 9090 | localhost | prometheus | http://localhost:9090 or http://prometheus:9090 |
- Every service exposes Prometheus metrics at
/metrics(listener worker listens on port 9464). - Prometheus HTTP API (no CORS; call from backend/proxy):
- Instant query:
GET /api/v1/query?query=<PROMQL> - Range query:
GET /api/v1/query_range?query=<PROMQL>&start=<ts>&end=<ts>&step=<s> - Example:
http://localhost:9090/api/v1/query?query=rate(flowise_proxy_http_request_duration_seconds_count[5m])
- Instant query:
- Front-end guidance: fetch Prometheus via your backend/proxy (or Grafana), not directly from the browser.
Note: Use range queries to fetch data for the past 15 minutes (for visualization). Range queries return time-series data points instead of single values.
async function getPrometheusData(query) {
const now = Math.floor(Date.now() / 1000); // Current timestamp (seconds)
const start = now - 15 * 60; // 15 minutes ago
const step = "30s";
const encodedQuery = encodeURIComponent(query);
const url = `http://localhost:9090/api/v1/query_range?query=${encodedQuery}&start=${start}&end=${now}&step=${step}`;
const response = await fetch(url);
const data = await response.json();
return data.data.result;
}- PromQL:
100 * (1 - avg by (instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))) - Range Query (15 min):
GET http://localhost:9090/api/v1/query_range?query=100%20*%20(1%20-%20avg%20by%20(instance)(rate(node_cpu_seconds_total%7Bmode%3D%22idle%22%7D%5B5m%5D)))&start=<now-900>&end=<now>&step=30s - JavaScript Example:
const query = '100 * (1 - avg by (instance)(rate(node_cpu_seconds_total{mode="idle"}[5m])))'; const cpuData = await getPrometheusData(query); // cpuData = [{ metric: {...}, values: [[timestamp, value], ...] }, ...]
- PromQL:
DCGM_FI_DEV_GPU_UTIL(ornvidia_gpu_utilizationdepending on exporter) - Range Query (15 min):
GET http://localhost:9090/api/v1/query_range?query=DCGM_FI_DEV_GPU_UTIL&start=<now-900>&end=<now>&step=30s - JavaScript Example:
const query = "DCGM_FI_DEV_GPU_UTIL"; const gpuData = await getPrometheusData(query);
- Used Memory:
node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes - Cached Memory:
node_memory_Cached_bytes - Buffer Memory:
node_memory_Buffers_bytes - Range Query Examples (15 min):
GET http://localhost:9090/api/v1/query_range?query=node_memory_MemTotal_bytes%20-%20node_memory_MemAvailable_bytes&start=<now-900>&end=<now>&step=30s GET http://localhost:9090/api/v1/query_range?query=node_memory_Cached_bytes&start=<now-900>&end=<now>&step=30s GET http://localhost:9090/api/v1/query_range?query=node_memory_Buffers_bytes&start=<now-900>&end=<now>&step=30s - JavaScript Example:
const usedMem = await getPrometheusData( "node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes" ); const cached = await getPrometheusData("node_memory_Cached_bytes"); const buffers = await getPrometheusData("node_memory_Buffers_bytes");
User login endpoint.
Request:
{
"identifier": "email@example.com OR username",
"password": "user_password"
}Response (200):
{
"message": "Login success",
"user": {
"email": "user@example.com",
"role": "student"
}
}Error Responses:
404 { "message": "User not found" }— User doesn't exist400 { "message": "Invalid credentials" }— Password mismatch500 { "message": "error details" }— Server error
Notes:
identifiercan be either email or username (usn)- Returns user info but no token (stateless or session-based approach)
- CORS enabled for localhost:3000
User registration endpoint.
Request:
{
"email": "newuser@example.com",
"usn": "username_or_student_id",
"password": "secure_password",
"role": "guest", // optional, defaults to "guest"
"photoProfile": "url_to_profile_pic" // optional
}Response (201):
{
"message": "User created successfully"
}Error Responses:
400 { "error": "User already exists" }— Email or USN already registered500 { "error": "error details" }— Server error
Notes:
- OTP is sent via broker/mail service for verification
- Role defaults to "guest" if not specified
- Common roles:
"student","staff","admin","guest"
Health check endpoint.
Response (200):
{ "status": "ok" }The broker service publishes events to RabbitMQ for asynchronous processing (email, logs, etc.).
Publish OTP event for email sending.
Request:
{
"type": "SEND_OTP",
"to": "user@example.com",
"otp": "123456",
"purpose": "register" // or "login", "reset_password", etc.
}Response (200):
{ "ok": true }Notes:
- Asynchronous: returns immediately, email sent in background
- Used during registration and password reset flows
- Listener service consumes this event and sends actual email
Publish log event for centralized logging.
Request:
{
"service": "authentication-service",
"level": "info", // or "warn", "error", "debug"
"event": "user_registered",
"message": "User john@example.com logged in",
"context": {
"userId": "123",
"extra": "metadata"
}
}Response (200):
{ "ok": true }Notes:
- Services use this to log events asynchronously; listener binds to
log.*and forwards into logger-service - Logger service consumes and stores logs; sampling/redaction applied automatically
- Useful for debugging and monitoring
Centralized log ingestion and retrieval.
Ingest a structured log event (HTTP path; services normally publish via broker).
Request:
{
"level": "info", // fatal|error|warn|info|debug|trace
"event": "otp_requested",
"message": "OTP issued for login",
"service": "authentication-service",
"requestId": "uuid-req",
"correlationId": "uuid-corr",
"userId": "optional-user-id",
"resource": "/auth/login",
"statusCode": 200,
"durationMs": 120,
"tags": ["auth", "otp"],
"context": {
"email": "user@example.com",
"usn": "USR123"
}
}Response (202):
{ "received": true, "sampled": true }Notes:
- Avoids sensitive keys (password/token/otp/secret/cookie/etc. are stripped).
- Typically invoked via
broker-service /publish/logto go through RabbitMQ. - Uses sampling; errors/warns always kept, info/debug sampled via
LOG_SAMPLE_RATE.
Fetch recent log entries from the current (date-based) log file. Requires JWT with role admin or staff.
Headers:
Authorization: Bearer <jwt>(same secret as authentication-serviceJWT_SECRET)
Query Params (optional):
date=YYYY-MM-DD(default: today)limit=200(max 1000)service=authenticaton-service(filter by service)level=error(filter by level)event=otp_requested(filter by event name)q=search text(search in message/context)
Response (200):
{
"logs": [
{
"level": "info",
"time": "2025-11-29T09:45:41.930Z",
"service": "authentication-service",
"event": "otp_requested",
"message": "OTP issued for login",
"requestId": "uuid-req",
"correlationId": "uuid-corr",
"resource": "/auth/login",
"statusCode": 200,
"durationMs": 120,
"context": { "email": "user@example.com", "usn": "USR123" }
}
],
"count": 1
}Auth & Roles:
- Uses JWT verification (
AUTH_JWT_SECRET/JWT_SECRET); requiresroleclaim to beadminorstaff.
Notes:
- Logs are stored as JSON lines in
logs/logger-YYYY-MM-DD.logwith retention control. - Logs are also persisted to MongoDB when
MONGO_URIis configured (defaults tomongodb://mongo:27017). - Pretty console output is enabled by default; disable with
LOG_PRETTY=false.
Export recent logs as CSV (human-friendly). Requires JWT with role admin or staff.
Headers:
Authorization: Bearer <jwt>
Query Params (optional):
date=YYYY-MM-DD(default: today)limit=500(max 1000)service,level,event,q(same filters as/logs)
Response (200):
text/csvwith columns:time, level, service, event, message, resource, statusCode, durationMs, requestId, correlationId, userId, tags, ip, context
Notes:
- Reads from MongoDB when available, otherwise falls back to local log files.
- Sets
Content-Dispositionfor easier download.
Health check endpoint.
Response (200):
{ "status": "ok" }Handles email sending (integrates with MailHog in dev).
Send an email.
Request:
{
"to": "recipient@example.com",
"subject": "Welcome to Helpdesk",
"text": "Plain text version (optional)",
"html": "<h1>Welcome!</h1><p>HTML email body</p>"
}Response (200):
{ "sent": true }Error Responses:
500 { "error": "error details" }— SMTP error
Notes:
- In development, emails are sent to MailHog (viewable at
http://localhost:8025) - Can use either
textorhtml(or both) - Used by listener service when processing OTP events
Health check endpoint.
Response (200):
{ "status": "ok" }Proxy for Flowise AI chatbot with knowledge base and chat history management.
Send a prediction request to a Flowise flow (single message).
Authentication: Required - Bearer token (JWT) via Authorization header
Request:
{
"question": "What are office hours?",
"sessionId": "uuid-or-session-id", // optional, generates if not provided
"userId": "user-id" // optional, for tracking
// any additional fields are passed through to Flowise
}Response (200):
{
"text": "AI response text here",
"sessionId": "uuid-of-session",
"sourceDocuments": [
{
"id": "doc1",
"name": "office_hours.pdf",
"relevance": 0.95
}
]
}Error Responses:
401 { "error": "Missing Bearer token" }— No authentication provided429 { "error": "Too many concurrent predictions, please retry shortly" }— Concurrency limit reached (default: 4)400 { "error": "question is required" }— Missing question field500 { "error": "Unable to complete prediction" }— Server error
Headers:
Authorization: Bearer <jwt>(required)Accept: text/event-stream(optional, switches to streaming mode)
Notes:
- Multi-round conversations: reuse the same
sessionIdfor follow-ups sessionIdis auto-generated if not provided; use returnedsessionIdfor next message in same sessionsourceDocumentslists knowledge base documents used for the answer- Chat interactions are persisted to MongoDB
- Additional request fields (beyond
question,sessionId,userId) are passed to Flowise - Metrics collected: request duration, CPU usage, token estimates, GPU utilization
Stream a prediction response (for real-time chat UI updates).
Authentication: Required - Bearer token (JWT) via Authorization header
Request:
{
"question": "What are office hours?",
"sessionId": "uuid-or-session-id", // optional
"userId": "user-id", // optional
"stream": true
}Response (200): Uses Server-Sent Events (SSE) stream. Headers:
Content-Type: text/event-stream
Streamed data format:
data: {"token": "The"}
data: {"token": " office"}
data: {"token": " is"}
...
event: done
data: "done"
Headers:
Authorization: Bearer <jwt>(required)Content-Type: text/event-stream(response)
Notes:
- Use
EventSourceor fetch with{ mode: 'no-cors', responseType: 'stream' }in frontend - Real-time token streaming for better UX
- Same session persistence as non-streaming endpoint
- Chat interactions persisted to MongoDB
- Metrics collected during streaming
- Stream automatically ends with
event: donemarker
List all chat sessions for a flow.
Authentication: Required - Bearer token (JWT) via Authorization header
Response (200):
{
"flowId": "2d844a72-3dc8-4475-8134-9f034015741f",
"sessions": [
{
"sessionId": "uuid-1",
"createdAt": "2025-11-21T10:30:00Z",
"updatedAt": "2025-11-21T10:45:00Z",
"messageCount": 4
},
{
"sessionId": "uuid-2",
"createdAt": "2025-11-21T09:00:00Z",
"updatedAt": "2025-11-21T09:15:00Z",
"messageCount": 2
}
]
}Headers:
Authorization: Bearer <jwt>(required)
Error Responses:
401 { "error": "Missing Bearer token" }— No authentication400 { "error": "flowId is required" }— Missing flow ID500 { "error": "Unable to load chat history" }— Server error
Get full chat history for a specific session.
Authentication: Required - Bearer token (JWT) via Authorization header
Response (200):
{
"flowId": "2d844a72-3dc8-4475-8134-9f034015741f",
"sessionId": "uuid-session-id",
"messages": [
{
"role": "user",
"content": "What are office hours?",
"timestamp": "2025-11-21T10:30:00Z"
},
{
"role": "assistant",
"content": "The office hours are...",
"sourceDocuments": [{ "id": "doc1", "name": "hours.pdf" }],
"timestamp": "2025-11-21T10:30:05Z"
},
{
"role": "user",
"content": "What about weekends?",
"timestamp": "2025-11-21T10:31:00Z"
},
{
"role": "assistant",
"content": "On weekends...",
"timestamp": "2025-11-21T10:31:10Z"
}
]
}Headers:
Authorization: Bearer <jwt>(required)
Error Responses:
401 { "error": "Missing Bearer token" }— No authentication400 { "error": "flowId and sessionId are required" }— Missing parameters404 { "error": "Chat session not found" }— Session doesn't exist500 { "error": "Unable to load chat session" }— Server error
Get knowledge base documents with metadata.
Authentication: Required - Bearer token with staff or admin role
Response (200):
{
"storeId": "d21759a2-d263-414e-b5a4-f2e5819d516e",
"documents": [
{
"storeId": "d21759a2-d263-414e-b5a4-f2e5819d516e",
"loaderId": "loader-1",
"kbId": "kb-entry-uuid",
"name": "Office Hours Policy",
"description": "Company office hours and schedule",
"filename": "office_hours.pdf",
"size": 245632,
"uploadedAt": "2025-11-20T15:30:00Z",
"metadata": {
"department": "HR",
"category": "policies"
}
}
],
"store": {
"id": "d21759a2-d263-414e-b5a4-f2e5819d516e",
"name": "Helpdesk KB"
}
}Headers:
Authorization: Bearer <jwt>(required, role must bestafforadmin)
Error Responses:
401 { "error": "Missing Bearer token" }— No authentication403 { "error": "Forbidden" }— Insufficient role permissions500 { "error": "Unable to read document list" }— Server error
List documents in knowledge base.
Authentication: Required - Bearer token with staff or admin role
Response (200):
[
{
"storeId": "d21759a2-d263-414e-b5a4-f2e5819d516e",
"loaderId": "loader-1",
"kbId": "kb-entry-uuid",
"name": "Office Hours Policy",
"description": "Company office hours and schedule",
"filename": "office_hours.pdf",
"size": 245632,
"uploadedAt": "2025-11-20T15:30:00Z",
"metadata": {
"department": "HR",
"category": "policies"
}
}
]Headers:
Authorization: Bearer <jwt>(required, role must bestafforadmin)
Get normalized knowledge base entries with full metadata from MongoDB KB store.
Authentication: Required - Bearer token with staff or admin role
Response (200):
[
{
"storeId": "d21759a2-d263-414e-b5a4-f2e5819d516e",
"loaderId": "loader-1",
"kbId": "kb-entry-uuid",
"name": "Office Hours Policy",
"description": "Company office hours and schedule",
"filename": "office_hours.pdf",
"size": 245632,
"uploadedAt": "2025-11-20T15:30:00Z",
"createdAt": "2025-11-20T15:30:00Z",
"updatedAt": "2025-11-20T15:30:00Z",
"metadata": {
"department": "HR",
"category": "policies",
"version": "1.0"
}
}
]Headers:
Authorization: Bearer <jwt>(required, role must bestafforadmin)
Notes:
- Returns normalized entries directly from MongoDB KB store
- Includes full metadata for each entry (name, description, creation/update timestamps)
/api/kb/entriesuses default store ID/api/kb/:storeId/entriesuses explicit store ID in URL
Get a single document with full details.
Authentication: Required - Bearer token with staff or admin role
Response (200):
{
"storeId": "d21759a2-d263-414e-b5a4-f2e5819d516e",
"loaderId": "loader-1",
"kbId": "kb-entry-uuid",
"name": "Office Hours Policy",
"description": "Company office hours and schedule",
"filename": "office_hours.pdf",
"size": 245632,
"uploadedAt": "2025-11-20T15:30:00Z",
"metadata": {
"department": "HR",
"category": "policies"
},
"createdAt": "2025-11-20T15:30:00Z",
"updatedAt": "2025-11-20T15:30:00Z"
}Headers:
Authorization: Bearer <jwt>(required, role must bestafforadmin)
Error Responses:
400 { "error": "loaderId is required" }— Missing loader ID404 { "error": "Unable to read knowledge base entry" }— Loader not found
Get chunks (text segments) from a document.
Authentication: Required - Bearer token with staff or admin role
Query Parameters:
page=1orpageNo=1— Page number (default: 1)
Response (200):
{
"loaderId": "loader-1",
"page": 1,
"chunks": [
{
"id": "chunk-1",
"content": "Office hours are Monday to Friday 9 AM to 5 PM...",
"page": 1
},
{
"id": "chunk-2",
"content": "Closed on public holidays...",
"page": 1
}
],
"totalPages": 5
}Headers:
Authorization: Bearer <jwt>(required, role must bestafforadmin)
Error Responses:
400 { "error": "loaderId is required" }— Missing loader ID502 { "error": "Flowise returned an unexpected HTML payload when requesting chunks" }— Flowise error
Update content of a specific chunk.
Authentication: Required - Bearer token with staff or admin role
Request:
{
"content": "Updated chunk content here...",
"metadata": {
"reviewed": true,
"reviewer": "admin@example.com"
}
}Response (200):
{
"chunkId": "chunk-1",
"loaderId": "loader-1",
"updated": true,
"updatedAt": "2025-11-21T14:15:00Z"
}Headers:
Authorization: Bearer <jwt>(required, role must bestafforadmin)
Error Responses:
400 { "error": "loaderId and chunkId are required" }— Missing parameters400 { "error": "content is required" }— Missing content field
Upload a document to knowledge base and automatically process it (extract text, split into chunks, embed).
Authentication: Required - Bearer token with staff or admin role
Request:
- Content-Type:
multipart/form-data - Fields:
file(required) — PDF, DOCX, CSV, TXT, XLS, XLSX, DOC (Allowed: .pdf, .doc, .docx, .csv, .xls, .xlsx)name(required) — Display name for the documentdescription(required) — Brief description of document contentmetadata(optional) — JSON string with additional custom metadatasourceUrl(optional) — Source URL of the documentreplaceExisting(optional) — "true" to replace existing document with same ID
Example (using FormData):
const formData = new FormData();
formData.append('file', file); // File object
formData.append('name', 'Office Hours Policy');
formData.append('description', 'Company office hours and business schedule');
formData.append('metadata', JSON.stringify({
department: 'HR',
category: 'policies',
version: '1.0'
}));
formData.append('sourceUrl', 'https://example.com/policies/hours');Response (200 - Sync Mode):
{
"docId": "loader-new-1",
"loaderId": "loader-new-1",
"kbId": "kb-entry-uuid",
"filename": "office_hours.pdf",
"storeId": "d21759a2-d263-414e-b5a4-f2e5819d516e",
"size": 245632,
"uploadedAt": "2025-11-21T14:00:00Z"
}Response (202 - Async Mode):
{
"jobId": "job-uuid",
"status": "queued"
}Headers:
Authorization: Bearer <jwt>(required, role must bestafforadmin)Content-Type: multipart/form-data
Error Responses:
400 { "error": "file field is required" }— No file provided400 { "error": "name is required" }— Missing document name400 { "error": "description is required" }— Missing description400 { "error": "Unsupported file type. Allowed types: PDF, DOC, DOCX, CSV, XLS, XLSX" }— Invalid file type500 { "error": "Unable to upsert document" }— Processing error500 { "error": "Unable to enqueue KB ingestion" }— Job queue error
Notes:
- If
ASYNC_KB=true(environment variable), returns 202 with jobId and queues processing - If
ASYNC_KB=false, processes synchronously and returns 200 with document details - Query job status with
GET /api/jobs/:jobId - File is automatically sanitized and stored in upload directory
Delete a document from knowledge base.
Authentication: Required - Bearer token with staff or admin role
Response (200):
{
"docId": "loader-1",
"loaderId": "loader-1",
"deleted": true,
"storeId": "d21759a2-d263-414e-b5a4-f2e5819d516e"
}Headers:
Authorization: Bearer <jwt>(required, role must bestafforadmin)
Error Responses:
400 { "error": "loaderId is required" }— Missing loader ID500 { "error": "Unable to delete document" }— Deletion error
Update or reprocess a document (update metadata, replace file, re-split chunks).
Authentication: Required - Bearer token with staff or admin role
Request:
- Content-Type:
multipart/form-data - Fields:
file(optional) — New file to replace documentname(optional) — Update document namedescription(optional) — Update descriptionmetadata(optional) — JSON string with updated metadatareplaceExisting(optional) — "false" to append chunks instead of replacing (default: true)
Example (using FormData):
const formData = new FormData();
formData.append('file', updatedFile); // Optional
formData.append('name', 'Updated Office Hours');
formData.append('description', 'Updated office schedule and policies');
formData.append('metadata', JSON.stringify({
version: '1.1',
lastReviewed: new Date().toISOString()
}));Response (200 - Sync Mode):
{
"docId": "loader-1",
"loaderId": "loader-1",
"kbId": "kb-entry-uuid",
"processedAt": "2025-11-21T14:10:00Z",
"status": "success",
"storeId": "d21759a2-d263-414e-b5a4-f2e5819d516e"
}Response (202 - Async Mode):
{
"jobId": "job-uuid",
"status": "queued"
}Headers:
Authorization: Bearer <jwt>(required, role must bestafforadmin)
Error Responses:
400 { "error": "loaderId is required" }— Missing loader ID400 { "error": "name is required" }— Name cannot be empty400 { "error": "description is required" }— Description cannot be empty500 { "error": "Unable to process document" }— Processing error
Reprocess a document (same as PUT, alternative endpoint).
Authentication: Required - Bearer token with staff or admin role
Request: Same as PUT endpoint (multipart/form-data with file, name, description, metadata)
Response (200 - Sync Mode): Same as PUT endpoint
Response (202 - Async Mode): Same as PUT endpoint
Headers:
Authorization: Bearer <jwt>(required, role must bestafforadmin)
Upsert (insert or update) documents via JSON (raw text content, not files).
Authentication: Required - Bearer token with staff or admin role
Request:
{
"docId": "manual-doc-1",
"text": "Office policy content here. Detailed text content that will be split into chunks...",
"name": "Office Policies",
"description": "Company office policies document",
"filename": "policies.pdf",
"metadata": {
"source": "manual",
"department": "HR",
"version": "1.0"
}
}Response (200 - Sync Mode):
{
"docId": "manual-doc-1",
"status": "upserted",
"chunksCount": 3,
"storeId": "d21759a2-d263-414e-b5a4-f2e5819d516e"
}Response (202 - Async Mode):
{
"jobId": "job-uuid",
"status": "queued"
}Headers:
Authorization: Bearer <jwt>(required, role must bestafforadmin)
Error Responses:
400 { "error": "Request body is required" }— Empty request body500 { "error": "Unable to upsert document" }— Processing error
Notes:
- If
ASYNC_KB=true, returns 202 with jobId - Directly upserts text content without file upload
- Text is automatically split into chunks and embedded
- Query job status with
GET /api/jobs/:jobId
Refresh/rebuild the knowledge base vector store (reindex all documents).
Authentication: Required - Bearer token with staff or admin role
Request: (empty body or {})
Response (200 - Sync Mode):
{
"status": "refresh_completed",
"totalDocuments": 5,
"storeId": "d21759a2-d263-414e-b5a4-f2e5819d516e"
}Response (202 - Async Mode):
{
"jobId": "job-uuid",
"status": "queued"
}Headers:
Authorization: Bearer <jwt>(required, role must bestafforadmin)
Error Responses:
500 { "error": "Unable to refresh document store" }— Refresh error
Notes:
- If
ASYNC_KB=true, returns 202 with jobId and queues refresh job - If
ASYNC_KB=false, processes synchronously and returns 200 - Rebuilds vector embeddings for all documents in the store
- Query job status with
GET /api/jobs/:jobId
Get the status of an async KB operation job.
Authentication: Required - Bearer token (JWT) via Authorization header
Response (200):
{
"jobId": "job-uuid",
"status": "processing",
"type": "kb.ingest",
"storeId": "d21759a2-d263-414e-b5a4-f2e5819d516e",
"result": {
"docId": "loader-123"
},
"error": null,
"createdAt": "2025-11-21T14:00:00Z",
"updatedAt": "2025-11-21T14:05:00Z"
}Headers:
Authorization: Bearer <jwt>(required)
Job Status Values:
queued— Job is waiting to be processedprocessing— Job is currently being processedsucceeded— Job completed successfully with resultfailed— Job failed with error message
Error Responses:
401 { "error": "Missing Bearer token" }— No authentication404 { "error": "Job not found" }— Job ID doesn't exist
Notes:
- Used to track async KB operations (ingest, reprocess, upsert, refresh)
- Poll this endpoint to check operation completion
- Successful jobs include result data in response
- Failed jobs include error message
Health check endpoint.
Response (200):
{ "status": "ok" }Notes:
- No authentication required
- Returns 200 if service is running
Prometheus metrics endpoint for monitoring.
Response (200):
Plain text Prometheus metrics format containing:
- HTTP request metrics (count, duration, status codes)
- Prediction metrics (tokens, cost, duration, GPU utilization)
- Custom application metrics
Notes:
- No authentication required
- Format:
# HELPand# TYPEheaders followed by metric data - Used by Prometheus scraper (
http://localhost:9090) - Includes latency buckets and quantiles
// 1. Login
const loginResponse = await fetch("http://localhost:3001/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
identifier: "user@example.com",
password: "password",
}),
});
const { user, message } = await loginResponse.json();
if (!loginResponse.ok) {
alert(message); // "User not found" or "Invalid credentials"
} else {
// Store user info or session
sessionStorage.setItem("user", JSON.stringify(user));
}// 2. Send a question to the chatbot
const chatResponse = await fetch(
"http://localhost:4000/api/v1/prediction/2d844a72-3dc8-4475-8134-9f034015741f",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
question: "What are office hours?",
sessionId: currentSessionId || undefined, // Reuse for multi-round
}),
}
);
const { message, sessionId, sourceDocuments } = await chatResponse.json();
// 3. Store sessionId for next turn
currentSessionId = sessionId;
// 4. Display answer and source documents
displayAnswer(message);
displaySources(sourceDocuments);const formData = new FormData();
const file = fileInput.files[0];
formData.append('file', file);
formData.append('name', 'IT Guidelines');
formData.append('description', 'IT department policies and guidelines for employees');
formData.append('metadata', JSON.stringify({
department: 'IT',
category: 'guides',
version: '2.1'
}));
const uploadResponse = await fetch('http://localhost:4000/api/kb/loaders', {
method: 'POST',
body: formData
});
if (!uploadResponse.ok) {
const error = await uploadResponse.json();
console.error('Upload failed:', error.error);
} else {
const { docId, loaderId, filename } = await uploadResponse.json();
console.log(`Uploaded: ${filename} (${loaderId})`);
}const formData = new FormData();
formData.append('name', 'Updated IT Guidelines');
formData.append('description', 'Updated policies and procedures');
formData.append('metadata', JSON.stringify({
version: '2.2',
lastUpdated: new Date().toISOString()
}));
const updateResponse = await fetch('http://localhost:4000/api/kb/loaders/loader-1', {
method: 'PUT',
body: formData
});
const result = await updateResponse.json();
console.log(`Updated: ${result.loaderId}`);const loaderResponse = await fetch('http://localhost:4000/api/kb/loaders/loader-1');
const document = await loaderResponse.json();
console.log(`Name: ${document.name}`);
console.log(`Description: ${document.description}`);
console.log(`Uploaded: ${document.uploadedAt}`);
console.log(`Size: ${document.size} bytes`);// Get chunks from document
const chunksResponse = await fetch('http://localhost:4000/api/kb/loaders/loader-1/chunks?page=1');
const { chunks } = await chunksResponse.json();
// Update a specific chunk
const chunkUpdate = await fetch('http://localhost:4000/api/kb/loaders/loader-1/chunks/chunk-1', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: 'Corrected or updated chunk text...',
metadata: {
reviewed: true,
reviewer: 'admin@company.com'
}
})
});
const updateResult = await chunkUpdate.json();
console.log('Chunk updated at:', updateResult.updatedAt);const response = await fetch(
"http://localhost:4000/api/v1/prediction/2d844a72-3dc8-4475-8134-9f034015741f/stream",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
question: "Explain our policies",
sessionId: currentSessionId,
stream: true,
}),
}
);
const reader = response.body.getReader();
const decoder = new TextDecoder();
let fullText = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split("\n");
for (const line of lines) {
if (line.startsWith("data: ")) {
const json = JSON.parse(line.slice(6));
fullText += json.token || json.text || "";
updateChatDisplay(fullText); // Live update UI
}
}
}All services follow a consistent error format:
Error Response (4xx/5xx):
{
"message": "Descriptive error message",
"error": "error details (optional)"
}Common Status Codes:
200— Success201— Created (registration, upload)400— Bad request (invalid input)401— Unauthorized (missing auth)404— Not found (user, session, document)500— Server error
- Allowed Origins:
http://localhost:3000,http://127.0.0.1:3000 - Allowed Methods: GET, POST, PUT, DELETE, OPTIONS
- Allowed Headers: Content-Type, Authorization, and others
- Type: JWT (JSON Web Token) Bearer authentication
- Location:
Authorization: Bearer <jwt>header (required for most endpoints) - Secret: Uses
AUTH_JWT_SECRETorJWT_SECRETenvironment variable - Token Format: JWT with
roleclaim (e.g.,student,staff,admin,guest)
Chat Endpoints (prediction, chat history):
- Required: Valid JWT token (any role)
- Returns: Authenticated user's chat data
Knowledge Base Endpoints (documents, chunks, uploads):
- Required: JWT token with
stafforadminrole - Returns: 403 Forbidden if user role is insufficient
- Chat Sessions: Use
sessionIdUUID for multi-round conversations - Persistence: Sessions stored in MongoDB
- Auto-generation:
sessionIdgenerated if not provided in request
NEXT_PUBLIC_AUTH_API_URL=http://localhost:3001
NEXT_PUBLIC_FLOWISE_PROXY_URL=http://localhost:4000
NEXT_PUBLIC_BROKER_URL=http://localhost:3002
NEXT_PUBLIC_MAIL_URL=http://localhost:3004# Authentication
AUTH_JWT_SECRET=your-jwt-secret-key
JWT_SECRET=your-jwt-secret-key
# Flowise Configuration
FLOWISE_URL=http://flowise:3000
FLOWISE_API_KEY=optional-api-key
FLOWISE_LOADER_NAME=fileLoader
FLOWISE_SPLITTER_NAME=recursiveCharacterTextSplitter
FLOWISE_VECTOR_STORE_NAME=pineconeUpsert
FLOWISE_EMBEDDING_NAME=openaiEmbedding
# Knowledge Base
DOCUMENT_STORE_ID=d21759a2-d263-414e-b5a4-f2e5819d516e
UPLOAD_DIR=/uploads
ASYNC_KB=true # Enable async KB processing
# Database
MONGO_URI=mongodb://mongo:27017/helpdesk
MONGO_DB_NAME=helpdesk
# Server
PORT=4000
READ_TIMEOUT_MS=60000
MAX_CONCURRENT_PREDICTIONS=4
# Metrics
COST_PER_1K_TOKENS_USD=0.002When ASYNC_KB=true, knowledge base operations (upload, reprocess, upsert, refresh) are queued for async processing via RabbitMQ instead of blocking the HTTP request.
- Client Request → POST
/api/kb/loaders(upload file) - Proxy Response → Returns 202 with
jobId - Job Queued → Event sent to RabbitMQ
kb.ingestqueue - Broker Listener → Picks up job from queue
- Internal Processing → Calls
POST /internal/jobs/kb/ingeston proxy - Completion → Job status updated to
succeededorfailed - Client Polling → Calls
GET /api/jobs/:jobIdto track progress
kb.ingest— File upload and vector embeddingkb.reprocess— Re-split and re-embed existing documentkb.upsert— Insert/update via JSON (raw text)kb.refresh— Rebuild entire vector store
These endpoints are for internal job processing via RabbitMQ and require WORKER_TOKEN authentication.
POST /internal/jobs/kb/ingest
POST /internal/jobs/kb/reprocess
POST /internal/jobs/kb/upsert
POST /internal/jobs/kb/refresh
POST /internal/jobs/status
Headers:
Authorization: Bearer <WORKER_TOKEN>
- Main Helpdesk Flow:
2d844a72-3dc8-4475-8134-9f034015741f
(Add more as you create flows in Flowise UI)
Field Changes:
message→text(response text field renamed)- Added
sessionIdto all responses (auto-generated if not provided) sourceDocumentsstill present for source attribution
All prediction requests automatically collect:
- Duration: Request processing time in seconds
- CPU Usage: CPU time consumed during request
- Token Estimate: Approximate tokens in response
- Cost Estimation: Estimated USD cost based on token count
- GPU Utilization: Sampled GPU usage if available
Metrics are exposed via GET /metrics endpoint in Prometheus format.
- Maximum concurrent predictions:
MAX_CONCURRENT_PREDICTIONS(default: 4) - Excess requests receive:
429 { "error": "Too many concurrent predictions, please retry shortly" } - Helps prevent resource exhaustion under high load
- Files stored on disk in
UPLOAD_DIRwith timestamp+name - Metadata automatically enriched with:
originalFileName,name,description - Unsupported file types rejected with error
- Multipart form-data validation includes MIME type checking
- Chat endpoints require valid JWT (any role)
- KB endpoints require JWT with
stafforadminrole - Health/Metrics endpoints require no authentication
- Internal endpoints require
WORKER_TOKENauthentication
- Verify
Authorization: Bearer <token>header is present - Check JWT is valid and not expired
- Verify
AUTH_JWT_SECRETorJWT_SECRETenv var is set
- KB endpoints require
stafforadminrole in JWT - Check user role claim in token payload
- Concurrent prediction limit reached
- Wait a moment and retry
- Increase
MAX_CONCURRENT_PREDICTIONSif needed
- Flowise returned HTML instead of JSON
- Check Flowise service is running and healthy
- Verify document/loader IDs are valid
- Verify
ASYNC_KB=trueand RabbitMQ connection - Check broker-service is running and connected
- Verify listener-service is consuming KB queue events
Use Postman, cURL, or VS Code REST Client:
### Health Check
GET http://localhost:4000/health
### Get Metrics
GET http://localhost:4000/metrics
### Chat with Knowledge Base (requires valid JWT token)
POST http://localhost:4000/api/v1/prediction/2d844a72-3dc8-4475-8134-9f034015741f
Authorization: Bearer your-jwt-token
Content-Type: application/json
{
"question": "What are office hours?"
}
### Stream Chat Response
POST http://localhost:4000/api/v1/prediction/2d844a72-3dc8-4475-8134-9f034015741f/stream
Authorization: Bearer your-jwt-token
Content-Type: application/json
{
"question": "Explain our policies",
"stream": true
}
### List Knowledge Base Documents (requires staff/admin role)
GET http://localhost:4000/api/kb
Authorization: Bearer your-staff-jwt-token
### Upload Document (requires staff/admin role)
POST http://localhost:4000/api/kb/loaders
Authorization: Bearer your-staff-jwt-token
Content-Type: multipart/form-data
(Form fields: file, name, description, metadata)
### Check Job Status
GET http://localhost:4000/api/jobs/job-uuid
Authorization: Bearer your-jwt-token