12 KiB
12 KiB
DreamChat API Specification
Overview
This document defines the REST API and WebSocket specifications for DreamChat.
- REST API: For CRUD operations, file uploads, and synchronous requests
- WebSocket: For real-time chat streaming
- OpenAPI: Auto-generated from NestJS decorators for frontend client generation
Base URL
Development: http://localhost:3000/api
Production: https://api.dreamchat.example/api
Authentication
JWT Token Flow
POST /api/auth/login
Content-Type: application/json
{
"username": "user@example.com",
"password": "securepassword"
}
Response:
{
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"refreshToken": "eyJhbGciOiJIUzI1NiIs...",
"expiresIn": 3600
}
Keycloak Integration
GET /api/auth/keycloak
Redirect to Keycloak login
Callback:
GET /api/auth/keycloak/callback?code=...
Response: { accessToken, refreshToken, expiresIn }
WebSocket Auth
WebSocket connections authenticate via query parameter:
ws://localhost:3000/chat?token=eyJhbGciOiJIUzI1NiIs...
REST API Endpoints
Authentication Module
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /auth/login |
Local login | Public |
| POST | /auth/refresh |
Refresh token | Public |
| POST | /auth/logout |
Logout | Bearer |
| GET | /auth/me |
Get current user | Bearer |
| GET | /auth/keycloak |
Keycloak login URL | Public |
| GET | /auth/keycloak/callback |
Keycloak callback | Public |
Users Module
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /users |
Register new user | Public |
| GET | /users/:id |
Get user profile | Bearer |
| PATCH | /users/:id |
Update user | Bearer (own only) |
| DELETE | /users/:id |
Delete user | Bearer (own/admin) |
Characters Module
// DTOs
class CreateCharacterDto {
name: string;
personalityPrompt: string;
backstory?: string;
attributes?: Record<string, any>;
avatarUrl?: string;
config?: Record<string, any>;
}
class CharacterResponseDto {
id: string;
name: string;
avatarUrl: string;
personalityPrompt: string;
backstory: string;
attributes: Record<string, any>;
createdAt: Date;
}
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /characters |
List user's characters | Bearer |
| POST | /characters |
Create character | Bearer |
| GET | /characters/:id |
Get character details | Bearer |
| PATCH | /characters/:id |
Update character | Bearer (owner) |
| DELETE | /characters/:id |
Delete character | Bearer (owner) |
Example Requests:
POST /api/characters
Authorization: Bearer {token}
Content-Type: application/json
{
"name": "Alice",
"personalityPrompt": "You are Alice, a curious and adventurous explorer...",
"backstory": "Alice grew up in a small village...",
"attributes": {
"traits": ["curious", "brave", "witty"],
"age": 25,
"species": "human",
"skills": ["navigation", "survival"]
}
}
Response: 201 Created
{
"id": "uuid",
"name": "Alice",
"personalityPrompt": "...",
"attributes": { ... },
"createdAt": "2026-02-23T10:00:00Z"
}
Conversations Module
class CreateConversationDto {
characterId: string;
title?: string;
}
class ConversationResponseDto {
id: string;
title: string;
character: CharacterSummaryDto;
messageCount: number;
createdAt: Date;
}
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /conversations |
List conversations | Bearer |
| POST | /conversations |
Create conversation | Bearer |
| GET | /conversations/:id |
Get conversation | Bearer |
| PATCH | /conversations/:id |
Update conversation | Bearer |
| DELETE | /conversations/:id |
Delete conversation | Bearer |
Messages Module
class CreateMessageDto {
content: string;
}
class MessageResponseDto {
id: string;
role: 'user' | 'assistant';
content: string;
tokensUsed?: number;
model?: string;
createdAt: Date;
}
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /conversations/:id/messages |
Get messages (paginated) | Bearer |
| POST | /conversations/:id/messages |
Send message (non-streaming) | Bearer |
| DELETE | /messages/:id |
Delete message | Bearer |
Import Module
class FileImportDto {
// Multipart form data
file: File;
}
class UrlImportDto {
url: string;
}
class ImportResponseDto {
id: string;
status: 'pending' | 'processing' | 'completed' | 'failed';
sourceName: string;
fileSize?: number;
content?: string;
errorMessage?: string;
createdAt: Date;
}
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /import/file |
Import file (txt, pdf, md) | Bearer |
| POST | /import/url |
Import from URL | Bearer |
| GET | /import/:id |
Get import status | Bearer |
| GET | /import |
List imports | Bearer |
| DELETE | /import/:id |
Delete import | Bearer |
File Upload Request:
POST /api/import/file
Authorization: Bearer {token}
Content-Type: multipart/form-data
------Boundary
Content-Disposition: form-data; name="file"; filename="story.txt"
Content-Type: text/plain
(file content here)
------Boundary--
Response: 202 Accepted
{
"id": "import-uuid",
"status": "processing",
"sourceName": "story.txt",
"createdAt": "2026-02-23T10:00:00Z"
}
URL Import Request:
POST /api/import/url
Authorization: Bearer {token}
Content-Type: application/json
{
"url": "https://archiveofourown.org/works/12345678"
}
Response: 202 Accepted
{
"id": "import-uuid",
"status": "processing",
"sourceName": "https://archiveofourown.org/works/12345678"
}
// If no scraper available:
Response: 400 Bad Request
{
"error": "UNSUPPORTED_URL",
"message": "No scraper available for this URL"
}
Story Module (Phase 2)
class CreateStoryBranchDto {
parentId?: string; // null for root
userDirection: string;
}
class StoryBranchResponseDto {
id: string;
conversationId: string;
parentId?: string;
title: string;
content: string;
userDirection: string;
depth: number;
children: StoryBranchResponseDto[];
createdAt: Date;
}
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /conversations/:id/story |
Get story tree | Bearer |
| POST | /conversations/:id/story/branches |
Create new branch | Bearer |
| GET | /story-branches/:id |
Get branch details | Bearer |
| PATCH | /story-branches/:id |
Update branch | Bearer |
| DELETE | /story-branches/:id |
Delete branch | Bearer |
Get Story Tree:
GET /api/conversations/conv-id/story
Authorization: Bearer {token}
Response:
{
"root": {
"id": "root-uuid",
"title": "The Beginning",
"content": "Once upon a time...",
"depth": 0,
"children": [
{
"id": "branch-1",
"title": "The Left Path",
"content": "You chose the left path...",
"userDirection": "Go left into the dark forest",
"depth": 1,
"children": []
},
{
"id": "branch-2",
"title": "The Right Path",
"content": "You chose the right path...",
"userDirection": "Go right towards the castle",
"depth": 1,
"children": []
}
]
}
}
Multi-Character Module (Phase 3)
class AddParticipantDto {
characterId: string;
autoRespond: boolean;
}
class ParticipantResponseDto {
id: string;
character: CharacterSummaryDto;
isActive: boolean;
autoRespond: boolean;
}
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /conversations/:id/participants |
List participants | Bearer |
| POST | /conversations/:id/participants |
Add participant | Bearer |
| DELETE | /conversations/:id/participants/:charId |
Remove participant | Bearer |
WebSocket Specification
Connection
const ws = new WebSocket('ws://localhost:3000/chat?token=JWT_TOKEN');
ws.onopen = () => {
console.log('Connected');
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
handleMessage(message);
};
Message Protocol
All WebSocket messages use JSON format:
interface WebSocketMessage {
type: string;
payload: any;
timestamp: string;
requestId?: string; // For correlating responses
}
Client → Server Events
1. Join Conversation
// Join a conversation room
{
"type": "JOIN_CONVERSATION",
"payload": {
"conversationId": "conv-uuid"
},
"requestId": "req-123"
}
// Response
{
"type": "CONVERSATION_JOINED",
"payload": {
"conversationId": "conv-uuid",
"history": [ /* last N messages */ ]
},
"requestId": "req-123"
}
2. Send Message (Streaming)
{
"type": "SEND_MESSAGE",
"payload": {
"conversationId": "conv-uuid",
"content": "Hello, how are you?",
"streaming": true // Enable streaming response
},
"requestId": "msg-456"
}
3. Stop Generation
{
"type": "STOP_GENERATION",
"payload": {
"conversationId": "conv-uuid"
}
}
4. Leave Conversation
{
"type": "LEAVE_CONVERSATION",
"payload": {
"conversationId": "conv-uuid"
}
}
Server → Client Events
1. Message Acknowledged
{
"type": "MESSAGE_ACK",
"payload": {
"messageId": "msg-uuid",
"status": "received"
},
"requestId": "msg-456"
}
2. Stream Chunk
{
"type": "STREAM_CHUNK",
"payload": {
"conversationId": "conv-uuid",
"chunk": " part of response",
"isComplete": false
},
"requestId": "msg-456"
}
3. Stream Complete
{
"type": "STREAM_COMPLETE",
"payload": {
"conversationId": "conv-uuid",
"messageId": "assistant-msg-uuid",
"content": "Full response text",
"tokensUsed": 150,
"model": "openai/gpt-4o"
},
"requestId": "msg-456"
}
4. Error
{
"type": "ERROR",
"payload": {
"code": "LLM_ERROR",
"message": "Failed to generate response",
"details": { ... }
},
"requestId": "msg-456"
}
Error Codes
| Code | Description | HTTP Status |
|---|---|---|
UNAUTHORIZED |
Invalid or expired token | 401 |
FORBIDDEN |
Insufficient permissions | 403 |
NOT_FOUND |
Resource not found | 404 |
VALIDATION_ERROR |
Invalid input data | 400 |
LLM_ERROR |
LLM provider error | 502 |
RATE_LIMITED |
Too many requests | 429 |
FILE_TOO_LARGE |
File exceeds 50MB | 413 |
UNSUPPORTED_URL |
No scraper for URL | 400 |
OpenAPI Configuration
NestJS Setup
// main.ts
const config = new DocumentBuilder()
.setTitle('DreamChat API')
.setDescription('Character simulation and chat API')
.setVersion('1.0')
.addBearerAuth()
.addTag('auth', 'Authentication endpoints')
.addTag('users', 'User management')
.addTag('characters', 'Character CRUD')
.addTag('conversations', 'Chat sessions')
.addTag('messages', 'Chat messages')
.addTag('import', 'Data import')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api/docs', app, document);
// Also serve raw JSON for client generation
writeFileSync('./openapi-spec.json', JSON.stringify(document, null, 2));
Frontend Client Generation
# Generate TypeScript client
npx openapi-generator-cli generate \
-i ./openapi-spec.json \
-g typescript-fetch \
-o ./apps/frontend/src/api/generated \
--additional-properties=supportsES6=true,npmName=dreamchat-api
# Or use Orval for React Query hooks
npx orval --config ./orval.config.js
Rate Limiting
| Endpoint | Limit |
|---|---|
| Auth endpoints | 10 req/min |
| General API | 100 req/min |
| File upload | 5 req/min |
| WebSocket messages | 60 msg/min |
| LLM streaming | 20 requests/min |
// Rate limit headers
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1645603200