Files
DreamChat/doc/api-spec.md

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