# Chat API

The Chat API processes messages through the LVNG engine. Two endpoints are available: a synchronous endpoint that returns a complete response, and a streaming endpoint that delivers incremental text and tool events via Server-Sent Events. Both support multi-modal input, personality routing for digital twins, and file attachments.

## POST /api/v2/chat

Send Message -- Process a message through the LVNG engine and return a complete response. Supports personality routing, file attachments, multi-modal images, and message type routing (chat, research, image, video). Rate limited to 100 requests per minute per IP, and 30 requests per minute per user. Supports JWT or API key authentication.

**Authentication required**

### Body Parameters

| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| message | string | Yes | -- | The user message to process. |
| platform | string | No | "api" | Source platform identifier (api, discord, slack, web, alexa). |
| userId | string | No | -- | User ID for conversation context and per-user rate limiting. |
| channelId | string (UUID) | No | -- | Channel UUID for conversation scoping. Messages are stored and retrieved by channel. |
| context | object | No | -- | Additional context passed to the AI engine (tool results, metadata, prior state). |
| personality | string | No | -- | Force a specific twin/agent personality (e.g. "steve-jobs", "paul-graham"). Defaults to LVNG routing. |
| streaming | boolean | No | false | Reserved flag (use /api/v2/chat/stream for actual streaming). |
| model | string | No | -- | Specific model override (e.g. "claude-opus-4-6"). Defaults to engine configuration. |
| messageType | string | No | "chat" | Routing hint: chat, research, image, or video. Research mode enables web search/scraping. |
| useFirecrawl | boolean | No | false | Enable web search and scraping for this request. |
| attachments | array | No | -- | File attachments as [{ id, name, type, size, url }]. Content is extracted and injected into the message context. |
| images | array | No | -- | Base64-encoded images for Claude vision: [{ filename, mediaType, base64 }]. |
| debug | boolean | No | false | Enable debug mode to include tool call traces in the response. |

### Example Request

```bash
curl -X POST https://api.lvng.ai/api/v2/chat \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Summarize the latest sales report",
    "userId": "f6a7b8c9-0d1e-2f3a-4b5c-6d7e8f901234",
    "channelId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "platform": "api"
  }'
```

### Response (200)

```json
{
  "messageId": "7e6d5c4b-3a29-1098-7654-321fedcba098",
  "reply": "Based on the latest sales report, Q1 revenue increased 23% year-over-year to $4.2M. Key highlights:\n\n- Enterprise segment grew 31% driven by 12 new accounts\n- Average deal size increased from $48K to $62K\n- Sales cycle shortened by 8 days on average",
  "personality": "lvng",
  "sessionId": "b8c9d0e1-f2a3-4b5c-6d7e-8f9012345678",
  "timestamp": "2026-03-19T14:32:18.000Z",
  "responseTime": 2340,
  "attachments": [],
  "confidence": 0.92,
  "metadata": {
    "toolsUsed": ["knowledge_search"],
    "model": "claude-sonnet-4-20250514"
  }
}
```

## POST /api/v2/chat/stream

Stream Response -- Stream an AI response via Server-Sent Events. Returns Content-Type: text/event-stream. Supports personality routing for twin conversations. A keepalive comment is sent every 15 seconds to prevent proxy timeouts.

**Authentication required**

### Body Parameters

| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| message | string | Yes | -- | The user message to process. |
| platform | string | No | "api" | Source platform identifier. |
| userId | string | No | -- | User ID for conversation context. |
| channelId | string (UUID) | No | -- | Channel UUID. Conversation history is loaded from this channel (last 10 messages). |
| workspaceId | string (UUID) | No | -- | Workspace context for the conversation. |
| threadId | string | No | -- | Thread ID for threaded conversations. |
| personality | string | No | -- | Twin/agent personality ID for routing. When set, the response comes from that persona. |
| model | string | No | -- | Model override (e.g. "claude-opus-4-6"). |
| context | object | No | -- | Additional context passed to the AI engine. |
| images | array | No | -- | Base64-encoded images for multi-modal input. |

### Example Request

```bash
curl -N -X POST https://api.lvng.ai/api/v2/chat/stream \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{
    "message": "Write a Python function to calculate Fibonacci numbers",
    "userId": "f6a7b8c9-0d1e-2f3a-4b5c-6d7e8f901234",
    "channelId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
  }'
```

### Response (200) -- Server-Sent Events

```
event: connected
data: {"streamId":"c4b3a291-0987-6543-21fe-dcba09876543","timestamp":"2026-03-19T14:35:00.000Z"}

event: text_delta
data: {"content":"Here's an efficient","accumulated_length":19}

event: text_delta
data: {"content":" Python function using","accumulated_length":40}

event: tool_start
data: {"tool":"code_interpreter","toolId":"tool_1a2b3c","timestamp":"2026-03-19T14:35:02.000Z"}

event: tool_call
data: {"tool":"code_interpreter","input":{"language":"python","code":"def fibonacci(n, memo={}):\n    if n <= 1: return n\n    if n in memo: return memo[n]\n    memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo)\n    return memo[n]\nprint(fibonacci(10))"}}

event: tool_result
data: {"tool":"code_interpreter","duration":120,"resultPreview":"55"}

event: tool_complete
data: {"tool":"code_interpreter","toolId":"tool_1a2b3c","success":true,"duration":120}

event: text_delta
data: {"content":"\nThe function returns 55 for n=10.","accumulated_length":312}

event: done
data: {"streamId":"c4b3a291-0987-6543-21fe-dcba09876543","reply":"Here's an efficient Python function using memoization...","personality":"lvng","responseTime":3200,"toolsUsed":["code_interpreter"],"confidence":0.95,"metadata":{"toolsUsed":["code_interpreter"],"knowledgeGraphQueried":false}}
```

## SSE Event Types

The streaming endpoint emits the following event types:

| Event | Description | Data Fields |
|-------|-------------|-------------|
| connected | Initial heartbeat confirming the stream is open. | streamId, timestamp |
| text_delta | Incremental text content. Append to your buffer. | content, accumulated_length |
| tool_start | A tool invocation has begun. | tool, toolId, timestamp |
| tool_call | Tool input parameters (sent after tool_start). | tool, input |
| tool_result | Tool output preview (truncated to 200 chars). | tool, duration, resultPreview |
| tool_complete | Tool execution finished with success/failure status. | tool, toolId, success, duration |
| done | Stream complete. Contains the full reply and metadata. | streamId, reply, personality, responseTime, toolsUsed, confidence, metadata |
| error | An error occurred during processing. | message, streamId |

## Client Example

A minimal TypeScript example that consumes the SSE stream:

```typescript
const response = await fetch('https://api.lvng.ai/api/v2/chat/stream', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    message: 'Explain quantum computing in simple terms',
    userId: 'f6a7b8c9-0d1e-2f3a-4b5c-6d7e8f901234',
    channelId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
  }),
})

const reader = response.body!.getReader()
const decoder = new TextDecoder()
let buffer = ''
let fullText = ''

while (true) {
  const { done, value } = await reader.read()
  if (done) break

  buffer += decoder.decode(value, { stream: true })
  const lines = buffer.split('\n')
  buffer = lines.pop() || ''

  let currentEvent = ''
  for (const line of lines) {
    if (line.startsWith('event: ')) {
      currentEvent = line.slice(7)
    } else if (line.startsWith('data: ')) {
      const data = JSON.parse(line.slice(6))

      switch (currentEvent) {
        case 'connected':
          console.log('Stream started:', data.streamId)
          break
        case 'text_delta':
          fullText += data.content
          process.stdout.write(data.content)
          break
        case 'tool_start':
          console.log('\nTool started:', data.tool)
          break
        case 'tool_complete':
          console.log('Tool done:', data.tool, data.duration + 'ms')
          break
        case 'done':
          console.log('\n---')
          console.log('Response time:', data.responseTime + 'ms')
          console.log('Tools used:', data.toolsUsed)
          break
        case 'error':
          console.error('Stream error:', data.message)
          break
      }
    }
  }
}
```
