API Endpoints¶
This reference documents all available API endpoints for the RAG Chatbot.
Base URL¶
All endpoints are relative to your installation path:
Tenant Identification¶
All endpoints require tenant identification. Provide the tenant using one of these methods:
| Method | Parameter | Example |
|---|---|---|
| POST Body | tenant_id | {"tenant_id": "my-tenant", ...} |
| Query String | tenant | ?tenant=my-tenant |
| HTTP Header | X-Tenant-ID | X-Tenant-ID: my-tenant |
The system checks these in order: POST body → Query parameter → HTTP header.
Best Practices
- Use POST body for JSON API requests (chat, customer-chat)
- Use query parameter for GET requests and file uploads
- Use HTTP header for admin endpoints
Authentication¶
| Endpoint | Auth Method | Key |
|---|---|---|
/chat | X-API-Key header | CHAT_API_KEY |
/upload | X-API-Key header | UPLOAD_API_KEY (required) |
/media-info | X-API-Key header | UPLOAD_API_KEY (required) |
/admin/* | X-API-Key header | ADMIN_API_KEY |
/admin-dashboard | HTTP Basic Auth | ADMIN_USERNAME / ADMIN_PASSWORD |
/api-debug/logs | HTTP Basic Auth | API_DEBUG_USERNAME / API_DEBUG_PASSWORD |
/customer-chat | None (public) | — |
/widget-config | None (public) | — |
Chat Endpoint¶
Send messages and receive AI-powered responses based on your knowledge base.
Request¶
Body Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tenant_id | string | Yes | Tenant identifier |
session_id | string | Yes | Unique identifier for the conversation |
message | string | Yes | User's question or message |
metadata | object | No | Optional filters for document search |
Metadata Filters:
{
"metadata": {
"speaker": "John", // Filter by speaker name
"topic": "AI", // Filter by topic
"file_id": "doc_123", // Filter to specific document
"document_type": "transcript" // Filter by document type
}
}
Response¶
Success (200 OK):
{
"response": "Based on the quarterly report, revenue increased by 15%...",
"sources": [
{
"filename": "Q3-Report.pdf",
"chunk_index": 3,
"similarity": 0.8543,
"keyword_score": 0.75,
"combined_score": 0.8225
}
],
"context_used": true,
"llm_provider": "openai"
}
No Relevant Content:
{
"response": "I don't have enough information to answer that question.",
"sources": [],
"context_used": false
}
Conversation Limit Reached:
{
"response": "This conversation has reached its maximum length. Click the **+** button in the header to start a new conversation.",
"sources": [],
"context_used": false,
"limit_reached": true
}
Example¶
curl -X POST https://your-domain.com/chat \
-H "Content-Type: application/json" \
-d '{
"tenant_id": "my-tenant",
"session_id": "user_12345",
"message": "What were the key findings in the Q3 report?"
}'
Rate Limits¶
| Limit | Value |
|---|---|
| Requests | 30 per minute |
| Message length | 10,000 characters |
| Identifier | Session ID + IP address |
Rate Limit Headers:
Upload Endpoint¶
Upload documents or import media for the knowledge base.
File Upload¶
Form Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
file | file | Yes | Document file to upload |
metadata | string | No | JSON string with custom metadata |
options | string | No | JSON string with processing options |
Processing Options:
URL Import¶
Form Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Yes | Media URL (YouTube, Vimeo, etc.) |
subtitle_langs | string | No | Comma-separated language codes (default: "en") |
metadata | string | No | JSON string with custom metadata |
Response (Single Document)¶
{
"success": true,
"message": "Document processed and imported successfully",
"document": {
"filename": "report.pdf",
"document_id": 42,
"document_type": "pdf",
"page_count": 15,
"word_count": 5230,
"tables_count": 3,
"processing_time_ms": 2450
}
}
Response (Playlist)¶
{
"success": true,
"message": "Playlist processed: 8 of 10 videos imported",
"assistant_message": "I've imported 8 videos from the playlist...",
"playlist": {
"title": "Training Series",
"video_count": 10,
"imported_count": 8
},
"documents": [
{
"title": "Video 1",
"document_id": 43,
"word_count": 1250
}
],
"failed_videos": [
{
"title": "Video 5",
"url": "https://...",
"error_type": "no_transcript",
"error": "No transcript available",
"retryable": true
}
]
}
Examples¶
File Upload:
curl -X POST "https://your-domain.com/upload?tenant=my-tenant" \
-H "X-API-Key: your-upload-api-key" \
-F "file=@document.pdf" \
-F 'metadata={"department": "Engineering"}'
YouTube Import:
curl -X POST "https://your-domain.com/upload?tenant=my-tenant" \
-H "X-API-Key: your-upload-api-key" \
-F "url=https://www.youtube.com/watch?v=dQw4w9WgXcQ" \
-F "subtitle_langs=en,es"
Rate Limits¶
| Limit | Value |
|---|---|
| Requests | 30 per minute |
| File size | 100 MB (configurable via MAX_UPLOAD_SIZE_MB) |
| Identifier | IP address |
Media Info Endpoint¶
Check if a media URL is supported before uploading. Useful for validating URLs and getting metadata. Requires UPLOAD_API_KEY authentication.
Request¶
Or:
POST /media-info?tenant=my-tenant
Content-Type: application/x-www-form-urlencoded
X-API-Key: your-upload-api-key
url=<media_url>
Response (Supported URL)¶
{
"success": true,
"supported": true,
"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
"type": "video",
"info": {
"title": "Video Title",
"duration": 212,
"uploader": "Channel Name"
}
}
Response (Unsupported URL)¶
{
"success": true,
"supported": false,
"url": "https://example.com/video.mp4",
"message": "URL is not from a supported media platform",
"supported_platforms": ["youtube.com", "youtu.be", "vimeo.com", "..."]
}
Example¶
curl "https://your-domain.com/media-info?tenant=my-tenant&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdQw4w9WgXcQ" \
-H "X-API-Key: your-upload-api-key"
Rate Limits¶
| Limit | Value |
|---|---|
| Requests | 30 per minute |
| Identifier | IP address |
Debug Endpoint¶
Inspect documents and test search queries. Useful for troubleshooting.
List Documents¶
Response:
{
"mode": "documents",
"stats": {
"total_documents": 15,
"total_chunks": 234,
"total_embeddings": 234
},
"documents": [
{
"id": 42,
"filename": "report.pdf",
"chunk_count": 12,
"embedding_count": 12,
"has_embeddings": true,
"source_url": null
}
]
}
Search Documents¶
View Document Details¶
Response:
{
"mode": "document",
"document": {
"id": 42,
"filename": "report.pdf",
"metadata": {...}
},
"chunks": [
{
"id": 101,
"chunk_index": 0,
"text_preview": "First 200 characters...",
"has_embedding": true
}
]
}
Test Search Query¶
Query Parameters:
| Parameter | Default | Description |
|---|---|---|
query | — | The search query to test |
threshold | 0.30 | Minimum combined score for results to be used |
Understanding the Threshold:
The threshold parameter controls how strict the search matching is. It represents the minimum combined score (0.0 to 1.0) a document chunk must achieve to be considered relevant. The combined score is calculated as:
This is useful for debugging because:
- Results not appearing? Lower the threshold (e.g.,
0.15) to see chunks that are being rejected and understand why - Irrelevant results appearing? Raise the threshold (e.g.,
0.40) to filter out weak matches - Compare scores — The response shows both
passed_resultsandrejected_by_threshold, letting you see exactly which chunks were filtered out and their scores
Debugging Tip
If the chat returns "I don't have enough information," test the same question here with a low threshold (0.1) to see what chunks exist and their similarity scores. This reveals whether the issue is missing content or a threshold that's too strict.
Response:
{
"mode": "query",
"query": "What is AI",
"threshold": 0.25,
"results_above_threshold": 3,
"results_below_threshold": 2,
"passed_results": [
{
"chunk_id": 101,
"filename": "ai-guide.pdf",
"vector_similarity": 0.82,
"keyword_score": 0.65,
"combined_score": 0.77
}
],
"rejected_by_threshold": [...],
"diagnosis": [
"3 results above threshold 0.25"
]
}
API Debug Logs Endpoint¶
View and manage API call logs. Requires authentication.
Authentication¶
HTTP Basic Authentication using credentials from .env:
- Username:
API_DEBUG_USERNAME - Password:
API_DEBUG_PASSWORD
List Sessions¶
Response:
{
"sessions": ["session_123", "session_456"],
"total_sessions": 2,
"log_path": "/var/www/chatbot/logs/api_debug"
}
Get Session Logs¶
Response:
{
"session_id": "session_123",
"message_count": 5,
"summary": {
"total_input_tokens": 1250,
"total_output_tokens": 850,
"total_tokens": 2100,
"total_duration_ms": 3450.25
},
"messages": [
{
"timestamp": "2026-02-02T10:30:00Z",
"provider": "openai",
"model": "gpt-5.2",
"tokens": {"input": 250, "output": 170},
"duration_ms": 690
}
]
}
Delete Session Logs¶
Response:
Rate Limits¶
| Limit | Value |
|---|---|
| Auth attempts | 5 per minute |
| Lockout | After 10 failures, 5-minute lockout |
Admin Endpoints¶
Management endpoints for the admin dashboard. All admin endpoints require the X-API-Key header with the tenant's ADMIN_API_KEY.
Authentication¶
List Documents¶
Query Parameters:
| Parameter | Default | Description |
|---|---|---|
page | 1 | Page number |
per_page | 20 | Items per page |
search | — | Search by filename |
Example:
curl -X GET "https://your-domain.com/admin/documents?page=1" \
-H "X-Tenant-ID: my-tenant" \
-H "X-API-Key: your_admin_api_key"
Response:
{
"data": [
{
"id": 42,
"filename": "report.pdf",
"document_type": "pdf",
"word_count": 5230,
"chunk_count": 12,
"created_at": "2026-02-01T10:30:00Z"
}
],
"pagination": {
"page": 1,
"per_page": 20,
"total": 45,
"total_pages": 3
}
}
Delete Document¶
Example:
curl -X DELETE "https://your-domain.com/admin/documents/42" \
-H "X-Tenant-ID: my-tenant" \
-H "X-API-Key: your_admin_api_key"
List Chat Sessions¶
Query Parameters:
| Parameter | Default | Description |
|---|---|---|
page | 1 | Page number |
per_page | 20 | Items per page |
Example:
curl -X GET "https://your-domain.com/admin/sessions" \
-H "X-Tenant-ID: my-tenant" \
-H "X-API-Key: your_admin_api_key"
Get Session Messages¶
Example:
curl -X GET "https://your-domain.com/admin/sessions/user_123/messages" \
-H "X-Tenant-ID: my-tenant" \
-H "X-API-Key: your_admin_api_key"
Delete Session¶
Tenant Prompts¶
Manage the tenant's custom LLM system prompt. The system prompt controls LLM behavior and is appended after hardcoded security rules.
Get current prompt:
Response:
{
"data": {
"tenant_id": "my-tenant",
"systemPrompt": "You are a helpful assistant...",
"isActive": true
},
"isCustom": true,
"defaultPrompt": "You are a helpful assistant..."
}
Save custom prompt:
Body:
Validation:
- Maximum length: 10,000 characters
- Rejected if it contains prompt injection patterns (e.g., "ignore previous instructions")
Reset to default:
Admin Chat¶
Send a message as an administrator (bypasses some restrictions):
Body:
{
"tenant_id": "my-tenant",
"session_id": "admin_session_123",
"message": "What documents do we have about Q3?"
}
Error Responses¶
All endpoints return consistent error formats:
Client Errors (4xx)¶
400 Bad Request:
401 Unauthorized:
429 Too Many Requests:
Server Errors (5xx)¶
500 Internal Server Error:
503 Service Unavailable:
Response Headers¶
All endpoints include these headers:
Security Headers:
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
CORS Headers:
CORS uses an origin whitelist configured via ALLOWED_ORIGINS in the tenant .env. Only listed origins receive CORS headers. If no origins are configured, cross-origin requests are blocked.
Access-Control-Allow-Origin: https://your-allowed-origin.com
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-API-Key
Access-Control-Max-Age: 3600
IPs that repeatedly send mismatched origins are rate-limited (10 per 5 minutes, then 403).
SDK Examples¶
PHP¶
<?php
$client = new GuzzleHttp\Client();
$response = $client->post('https://your-domain.com/chat', [
'json' => [
'tenant_id' => 'my-tenant',
'session_id' => 'user_123',
'message' => 'What is in the report?'
]
]);
$data = json_decode($response->getBody(), true);
echo $data['response'];
Python¶
import requests
response = requests.post(
'https://your-domain.com/chat',
json={
'tenant_id': 'my-tenant',
'session_id': 'user_123',
'message': 'What is in the report?'
}
)
data = response.json()
print(data['response'])
JavaScript¶
const response = await fetch('https://your-domain.com/chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
tenant_id: 'my-tenant',
session_id: 'user_123',
message: 'What is in the report?'
})
});
const data = await response.json();
console.log(data.response);