Skip to main content

Overview

While the embeddable widget works for most websites, you may want a fully custom chat experience. The IllumiChat API gives you direct access to create conversations, send messages, and receive streamed AI responses — letting you build any interface you can imagine.

Authentication

All API requests require an API token. Tokens are scoped to a workspace and inherit the creating user’s permissions.
1

Generate an API Token

In the IllumiChat dashboard, go to Workspace Settings > API Tokens and click Create Token. Give it a descriptive name like “Custom Chat App.”
2

Copy and Store the Token

Copy the generated token immediately. It is only shown once.The token has the format: ic_ followed by 40 random characters.
3

Use the Token in Requests

Include the token in the Authorization header of every API request:
curl -X POST https://beta.illumichat.com/api/chat \
  -H "Authorization: Bearer ic_your-token-here" \
  -H "Content-Type: application/json" \
  -d '{"assistantId": "your-assistant-id", "messages": [{"role": "user", "content": "Hello"}]}'
Never expose your API token in client-side JavaScript. Route requests through your own backend server. See the Backend Proxy Pattern section below.

Sending Messages

The chat endpoint accepts messages and returns a streamed response.

Request Format

POST https://beta.illumichat.com/api/chat
Content-Type: application/json
Authorization: Bearer ic_your-token-here
{
  "assistantId": "asst_abc123",
  "chatId": "chat_xyz789",
  "messages": [
    { "role": "user", "content": "What features does your enterprise plan include?" }
  ]
}
FieldRequiredDescription
assistantIdYesThe assistant to chat with
messagesYesArray of message objects with role and content
chatIdNoExisting chat ID to continue a conversation; omit to start new

Handling Streaming Responses

The API returns responses as Server-Sent Events (SSE). This allows your interface to display text as it is generated.

Event Types

Event TypeDescriptionPayload
text-deltaA chunk of generated texttextDelta: the text fragment
tool-callAI is invoking a tool (web search, MCP)toolName, args
tool-resultResult from a tool calltoolName, result
finishStream is completefinishReason: stop, length, or error

Parsing the Stream

async function sendMessage(messages) {
  const response = await fetch('/api/chat-proxy', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ assistantId: 'asst_abc123', messages }),
  });

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let assistantMessage = '';

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

    const chunk = decoder.decode(value, { stream: true });
    const lines = chunk.split('\n').filter(line => line.startsWith('data: '));

    for (const line of lines) {
      const data = JSON.parse(line.slice(6));
      switch (data.type) {
        case 'text-delta':
          assistantMessage += data.textDelta;
          updateUI(assistantMessage);
          break;
        case 'tool-call':
          showToolIndicator(data.toolName);
          break;
        case 'finish':
          finalizeMessage(assistantMessage);
          break;
      }
    }
  }
}

Minimal Working Example

A complete HTML file that implements a basic chat interface.
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Chat Interface</title>
  <style>
    body { font-family: system-ui, sans-serif; max-width: 600px; margin: 40px auto; padding: 0 20px; }
    #messages { border: 1px solid #e5e7eb; border-radius: 8px; padding: 16px; height: 400px; overflow-y: auto; }
    .message { margin-bottom: 12px; padding: 8px 12px; border-radius: 8px; }
    .user { background: #6366f1; color: white; margin-left: 20%; text-align: right; }
    .assistant { background: #f3f4f6; margin-right: 20%; }
    #input-area { display: flex; gap: 8px; margin-top: 16px; }
    #user-input { flex: 1; padding: 10px; border: 1px solid #d1d5db; border-radius: 6px; }
    button { padding: 10px 20px; background: #6366f1; color: white; border: none; border-radius: 6px; cursor: pointer; }
  </style>
</head>
<body>
  <h2>Chat</h2>
  <div id="messages"></div>
  <div id="input-area">
    <input type="text" id="user-input" placeholder="Type a message..." />
    <button onclick="handleSend()">Send</button>
  </div>
  <script>
    const PROXY_URL = '/api/chat-proxy';
    const ASSISTANT_ID = 'your-assistant-id';
    let history = [];

    async function handleSend() {
      const input = document.getElementById('user-input');
      const text = input.value.trim();
      if (!text) return;
      input.value = '';
      appendMsg('user', text);
      history.push({ role: 'user', content: text });

      const res = await fetch(PROXY_URL, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ assistantId: ASSISTANT_ID, messages: history }),
      });

      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let reply = '';
      const el = appendMsg('assistant', '');

      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        for (const line of decoder.decode(value, { stream: true }).split('\n')) {
          if (!line.startsWith('data: ')) continue;
          try {
            const d = JSON.parse(line.slice(6));
            if (d.type === 'text-delta') { reply += d.textDelta; el.textContent = reply; }
          } catch (_) {}
        }
      }
      history.push({ role: 'assistant', content: reply });
    }

    function appendMsg(role, text) {
      const c = document.getElementById('messages');
      const el = document.createElement('div');
      el.className = 'message ' + role;
      el.textContent = text;
      c.appendChild(el);
      c.scrollTop = c.scrollHeight;
      return el;
    }

    document.getElementById('user-input')
      .addEventListener('keydown', e => { if (e.key === 'Enter') handleSend(); });
  </script>
</body>
</html>

Backend Proxy Pattern

Route chat requests through your own backend to keep API tokens secure.
import express from 'express';

const app = express();
app.use(express.json());

const API_TOKEN = process.env.ILLUMICHAT_API_TOKEN;

app.post('/api/chat-proxy', async (req, res) => {
  const { assistantId, messages, chatId } = req.body;

  const upstream = await fetch('https://beta.illumichat.com/api/chat', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${API_TOKEN}`,
    },
    body: JSON.stringify({ assistantId, messages, chatId }),
  });

  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');

  const reader = upstream.body.getReader();
  while (true) {
    const { done, value } = await reader.read();
    if (done) { res.end(); break; }
    res.write(value);
  }
});

app.listen(3001, () => console.log('Proxy running on port 3001'));
The proxy streams the response directly from IllumiChat to the client without buffering. Your server acts as a pass-through that attaches the authentication header.

Error Handling

CodeMeaningAction
200SuccessProcess the stream
400Bad requestCheck request body format
401UnauthorizedVerify your API token
404Assistant not foundCheck the assistant ID
429Rate limitedWait and retry with exponential backoff
500Server errorRetry after a brief delay

Reconnection Strategy

async function sendWithRetry(messages, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await sendMessage(messages);
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
      const delay = Math.pow(2, attempt) * 1000;
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

Troubleshooting

  • Verify your API token is valid and not revoked
  • Ensure the assistant ID exists and belongs to the token’s workspace
  • Check that the messages array is not empty
  • Implement retry logic for network interruptions
  • Check for the finish event to confirm the response completed
  • The maximum response duration is 60 seconds
  • Direct browser requests to the IllumiChat API will fail due to CORS restrictions
  • Use a backend proxy as described above

Next Steps