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.
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.”
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.
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.
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?" }
]
}
Field Required Description assistantIdYes The assistant to chat with messagesYes Array of message objects with role and content chatIdNo Existing 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 Type Description Payload text-deltaA chunk of generated text textDelta: the text fragmenttool-callAI is invoking a tool (web search, MCP) toolName, argstool-resultResult from a tool call toolName, resultfinishStream is complete finishReason: 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 : 600 px ; margin : 40 px auto ; padding : 0 20 px ; }
#messages { border : 1 px solid #e5e7eb ; border-radius : 8 px ; padding : 16 px ; height : 400 px ; overflow-y : auto ; }
.message { margin-bottom : 12 px ; padding : 8 px 12 px ; border-radius : 8 px ; }
.user { background : #6366f1 ; color : white ; margin-left : 20 % ; text-align : right ; }
.assistant { background : #f3f4f6 ; margin-right : 20 % ; }
#input-area { display : flex ; gap : 8 px ; margin-top : 16 px ; }
#user-input { flex : 1 ; padding : 10 px ; border : 1 px solid #d1d5db ; border-radius : 6 px ; }
button { padding : 10 px 20 px ; background : #6366f1 ; color : white ; border : none ; border-radius : 6 px ; 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
Code Meaning Action 200Success Process the stream 400Bad request Check request body format 401Unauthorized Verify your API token 404Assistant not found Check the assistant ID 429Rate limited Wait and retry with exponential backoff 500Server error Retry 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
Stream returns empty or no data
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
Partial responses or cut-off text
Implement retry logic for network interruptions
Check for the finish event to confirm the response completed
The maximum response duration is 60 seconds
CORS errors in the browser
Direct browser requests to the IllumiChat API will fail due to CORS restrictions
Use a backend proxy as described above
Next Steps