# 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 ```http POST /api/auth/login Content-Type: application/json { "username": "user@example.com", "password": "securepassword" } Response: { "accessToken": "eyJhbGciOiJIUzI1NiIs...", "refreshToken": "eyJhbGciOiJIUzI1NiIs...", "expiresIn": 3600 } ``` ### Keycloak Integration ```http 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 ```typescript // DTOs class CreateCharacterDto { name: string; personalityPrompt: string; backstory?: string; attributes?: Record; avatarUrl?: string; config?: Record; } class CharacterResponseDto { id: string; name: string; avatarUrl: string; personalityPrompt: string; backstory: string; attributes: Record; 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:** ```http 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 ```typescript 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 ```typescript 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 ```typescript 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:** ```http 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:** ```http 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) ```typescript 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:** ```http 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) ```typescript 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 ```javascript 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: ```typescript interface WebSocketMessage { type: string; payload: any; timestamp: string; requestId?: string; // For correlating responses } ``` ### Client → Server Events #### 1. Join Conversation ```typescript // 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) ```typescript { "type": "SEND_MESSAGE", "payload": { "conversationId": "conv-uuid", "content": "Hello, how are you?", "streaming": true // Enable streaming response }, "requestId": "msg-456" } ``` #### 3. Stop Generation ```typescript { "type": "STOP_GENERATION", "payload": { "conversationId": "conv-uuid" } } ``` #### 4. Leave Conversation ```typescript { "type": "LEAVE_CONVERSATION", "payload": { "conversationId": "conv-uuid" } } ``` ### Server → Client Events #### 1. Message Acknowledged ```typescript { "type": "MESSAGE_ACK", "payload": { "messageId": "msg-uuid", "status": "received" }, "requestId": "msg-456" } ``` #### 2. Stream Chunk ```typescript { "type": "STREAM_CHUNK", "payload": { "conversationId": "conv-uuid", "chunk": " part of response", "isComplete": false }, "requestId": "msg-456" } ``` #### 3. Stream Complete ```typescript { "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 ```typescript { "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 ```typescript // 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 ```bash # 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 | ```typescript // Rate limit headers X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1645603200 ```