KeepAI exposes a REST API at /api/v1 and a WebSocket endpoint at /ws/chat. Live interactive documentation is available at /docs (Swagger) and /redoc (ReDoc).
http://localhost:8000/api/v1
WebSocket URL (derived from host):
ws://<host>/ws/chat?token=<jwt>
Most endpoints require JWT authentication. Obtain a token via login, then include it in the Authorization header:
Authorization: Bearer <your_access_token>
GET /health/liveAlways returns 200 if the process is running. No dependencies.
Response 200 OK
{ "status": "ok" }GET /health/readyVerifies database and Ollama connectivity before accepting traffic.
Response 200 OK
{ "status": "ready", "db": "ok", "llm": "ok" }Response 503 Service Unavailable
{ "detail": { "status": "unavailable", "db": "could not connect to database" } }POST /api/v1/auth/register| Field | Type | Required | Description |
|---|---|---|---|
email |
string | yes | Valid email address |
password |
string | yes | Password (min 8 chars recommended) |
role |
string | no | "admin" or "user" (defaults to "user") |
curl -X POST http://localhost:8000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "securepassword"}'Response 201 Created
{ "id": 1, "email": "user@example.com", "is_active": true, "role": "user" }POST /api/v1/auth/loginUses OAuth2 form data.
| Field | Type | Required | Description |
|---|---|---|---|
username |
string | yes | User's email |
password |
string | yes | User's password |
curl -X POST http://localhost:8000/api/v1/auth/login \
-F "username=user@example.com" \
-F "password=securepassword"Response 200 OK
{ "access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer" }Token expires in 30 minutes by default.
POST /api/v1/prompts| Field | Type | Required | Description |
|---|---|---|---|
prompt_text |
string | yes | The prompt to send to the LLM |
model_name |
string | no | Model name. Defaults to OLLAMA_MODEL |
system_prompt |
string | no | System instructions |
task_type |
string | no | Task type for model routing |
temperature |
float | no | Sampling temperature (0.0-2.0) |
top_p |
float | no | Nucleus sampling (0.0-1.0) |
max_tokens |
int | no | Maximum tokens to generate |
curl -X POST http://localhost:8000/api/v1/prompts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"prompt_text": "Explain ML in 3 points.", "model_name": "llama3"}'Response 201 Created
{
"id": 42,
"prompt_text": "Explain ML in 3 points.",
"response_text": "- Machine learning is a subset of AI...",
"model_name": "llama3",
"processing_time_ms": 4523,
"meta_data": {},
"created_at": "2026-01-15T10:30:00Z",
"user_id": 1
}POST /api/v1/prompts/streamSame body as Generate. Returns SSE stream. Not persisted.
data: Machine
data: learning
data: [DONE]
GET /api/v1/prompts?skip=0&limit=20| Param | Type | Default | Description |
|---|---|---|---|
skip |
int | 0 | Records to skip |
limit |
int | 20 | Max records per page |
Response 200 OK
{ "items": [...], "total": 1, "skip": 0, "limit": 20 }GET /api/v1/prompts/{prompt_id}Scoped to the authenticated user.
POST /api/v1/extract-invoice| Field | Type | Required | Description |
|---|---|---|---|
text_content |
string | yes | Raw invoice text |
model_name |
string | no | Model name |
Returns structured JSON with invoice_number, vendor_name, date, items, total_amount, currency.
POST /api/v1/chat| Field | Type | Required | Description |
|---|---|---|---|
messages |
array | yes | [{role, content}] roles: user/assistant/system |
model_name |
string | no | Model name |
system_prompt |
string | no | Prepended as system message |
temperature |
float | no | 0.0-2.0 |
top_p |
float | no | 0.0-1.0 |
max_tokens |
int | no | Max tokens |
POST /api/v1/chat/streamSame body as Chat. Returns SSE stream.
POST /api/v1/conversations{ "title": "My conversation", "model_name": "llama3" }GET /api/v1/conversations?skip=0&limit=20Returns conversations with message_count.
GET /api/v1/conversations/{conversation_id}Returns conversation with all messages.
PUT /api/v1/conversations/{conversation_id}/title?title=New+TitleDELETE /api/v1/conversations/{conversation_id}POST /api/v1/conversations/{conversation_id}/chat{ "message": "Hello", "model_name": "llama3" }ws://<host>/ws/chat?token=<jwt>
Send: list_conversations
{ "type": "list_conversations" }Response: { "type": "conversations", "items": [...] }
Send: get_conversation
{ "type": "get_conversation", "conversation_id": 1 }Response: { "type": "conversation", "id": 1, "title": "...", "messages": [...], "user_id": 1, "created_at": "...", "updated_at": "..." }
Send: chat
{
"type": "chat",
"conversation_id": 1,
"message": "Hello!",
"model_name": "llama3",
"system_prompt": "Be helpful",
"temperature": 0.7,
"top_p": 0.9
}Response: { "type": "chat_response", "response_text": "...", "conversation_id": 1 }
Send: delete_conversation
{ "type": "delete_conversation", "conversation_id": 1 }Response: { "type": "conversation_deleted", "conversation_id": 1 }
Send: ping
{ "type": "ping" }Response: { "type": "pong" }
GET /api/v1/api-keysPOST /api/v1/api-keys{ "name": "My Key" }Response 201 Created
{ "id": 1, "name": "My Key", "key": "ka_...", "is_active": true, "created_at": "..." }The key value is only shown once at creation.
DELETE /api/v1/api-keys/{key_id}GET /api/v1/analytics/stats?days=7| Param | Type | Default | Description |
|---|---|---|---|
days |
int | null (all time) | Number of days to look back |
Response 200 OK
{
"total_requests": 150,
"total_processing_time_ms": 450000,
"avg_processing_time_ms": 3000.0,
"requests_by_model": { "llama3": 120, "mistral": 30 },
"requests_by_action": { "prompt": 100, "chat": 50 },
"requests_today": 12
}GET /api/v1/analytics/admin/user-statsReturns per-user stats for all users. Requires admin.
GET /api/v1/documentsPOST /api/v1/documents/uploadMultipart form with file field.
GET /api/v1/documents/{document_id}Returns document with content_text and chunk_count.
DELETE /api/v1/documents/{document_id}GET /api/v1/documents/search?q=keyword&top_k=5| Param | Type | Default | Description |
|---|---|---|---|
q |
string | required | Search query |
top_k |
int | 5 | Max results (max 50) |
POST /api/v1/documents/search{ "query": "keyword", "top_k": 5 }POST /api/v1/documents/query{ "query": "What is this document about?", "top_k": 5 }Response 200 OK
{ "results": [...], "response": "Found 5 relevant documents" }POST /api/v1/documents/{document_id}/embedResponse 200 OK
{ "status": "embedded" }GET /api/v1/modelsReturns models available in Ollama with name, size, digest, details.
POST /api/v1/models/pull| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Model name, e.g., "llama3" |
insecure |
boolean | no | Allow insecure connections |
Requires models:manage permission (admin).
DELETE /api/v1/models/{model_name}Requires models:manage permission (admin).
GET /api/v1/admin/users?skip=0&limit=100Requires users:read permission (admin).
GET /api/v1/admin/all-prompts?skip=0&limit=100Requires prompts:read_all permission (admin).
{ "detail": "Not authenticated" }{ "detail": "Insufficient permissions" }{ "detail": "Resource not found" }{
"detail": [
{ "loc": ["body", "email"], "msg": "field required", "type": "value_error.missing" }
]
}{ "detail": "Rate limit exceeded. Please slow down." }{ "detail": "Internal server error" }Rate limiting is implemented via slowapi (configurable via RATE_LIMIT_LLM env var, default: 20/minute). Limits are applied per user (JWT email) or fall back to client IP. See Configuration for details.
See Changelog for version history.