Chat API
Integrate AlonChat conversations into your applications via the REST API
Chat API#
Send messages and receive AI responses programmatically through the AlonChat Chat API.
Overview#
The Chat API lets you:
- Send a customer message to your AI agent
- Receive a complete reply or a streaming response
- Continue multi-turn conversations with a conversation ID
- Track credits used for each request
API, MCP, and CLI access are available on every paid plan. Active trials may access gated features during the trial, subject to trial limits.
The same endpoint is documented on your agent's Deploy → API page.
Quick Start#
1. Get Your API Key#
- Go to Project Settings → API Keys and create a key (optionally restrict it to one agent).
- Open your agent's Deploy → API page for the endpoint and Agent ID.
- Send the key as a Bearer token from your server only.
Keys look like sk-… and are shown once at creation.
2. Make Your First Request#
curl -X POST https://alonchat.com/api/v1/chat/{agentId} \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: first-message-001" \
-d '{
"message": "Hello, what are your business hours?"
}'
3. Handle the Response#
{
"response": "We're open Monday–Saturday, 9AM to 6PM. Closed on Sundays and holidays!",
"conversation_id": "550e8400-e29b-41d4-a716-446655440000",
"credits_used": 5,
"credits_remaining": 995
}
Save conversation_id and send it on later requests to keep the same thread.
Authentication#
Authorization: Bearer YOUR_API_KEY
API keys are managed in Project Settings → API Keys. You can optionally restrict a key to one agent. See the Authentication guide.
Security: Use API keys only on your server. Never put them in frontend code, mobile apps, or public repositories.
Endpoint#
POST /api/v1/chat/{agentId}
Request Body#
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | User message (1–10,000 characters). Still required when you send a Workflow interaction. |
conversation_id | uuid | No | Continue an existing conversation. Omit to start a new one. |
stream | boolean | No | Set true for Server-Sent Events (SSE). Default false. |
idempotency_key | string | Yes | 1–255 characters. Required for a billable turn. Can also be sent as the Idempotency-Key or X-Idempotency-Key header. |
interaction | object | No | A workflow_button selection: type, runId, nodeId, and payload from the previous presentation. It does not start an arbitrary Workflow. |
Example (non-streaming)#
const response = await fetch(`https://alonchat.com/api/v1/chat/${agentId}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: 'What products do you offer?',
conversation_id: '550e8400-e29b-41d4-a716-446655440000',
idempotency_key: crypto.randomUUID(),
}),
})
const data = await response.json()
console.log(data.response)
console.log(data.conversation_id)
console.log(data.credits_used)
Success Response#
| Field | Type | Description |
|---|---|---|
response | string | Agent reply text. Empty when the customer message was accepted while AI replies are paused. |
conversation_id | uuid | Conversation to continue on the next turn |
credits_used | number | Credits consumed by this request |
credits_remaining | number | Credits left after this request. Returned on chat even when the key cannot read billing. |
presentation | object | Present only when this turn has Workflow messages, buttons, or media to render. Version is 1. |
customer_message | object | Present when the message was stored while AI replies are paused. ai_reply is paused. |
The TypeScript SDK maps conversation_id, credits_used, credits_remaining, and
customer_message to camelCase. presentation keeps the same name.
presentation can include ordered messages, buttons, image URLs, and media URLs. Render only the
fields that are actually present. API chat does not reproduce a custom widget: forms use a
conversational fallback, and asynchronous follow-ups are not part of this response. Test interactive
widget layout in the embedded web widget, not with this endpoint alone.
To continue a Workflow button, send a new idempotency key and an interaction copied from that
presentation. A selection that is no longer current returns 409 WORKFLOW_INTERACTION_STALE.
Sending an interaction while AI replies are paused returns 409 AI_REPLY_PAUSED.
An identical completed request is replayed with 200 and X-AlonChat-Idempotent-Replay: true.
That replay does not run the turn again. Diagnostic fields such as model and timing can also
appear; do not depend on them.
Streaming Responses#
Set "stream": true to receive Server-Sent Events. Data lines contain JSON after data: . Network
chunks can split a line, so buffer incomplete lines before parsing. The
SDK also provides streamMessage(...) for this.
const response = await fetch(`https://alonchat.com/api/v1/chat/${agentId}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: 'Tell me about your services',
stream: true,
idempotency_key: crypto.randomUUID(),
}),
})
if (!response.ok || !response.body) {
throw new Error(`Chat request failed: ${response.status}`)
}
const reader = response.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
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() ?? ''
for (const line of lines) {
if (!line.startsWith('data: ')) continue
const event = JSON.parse(line.slice(6))
if (event.type === 'token') {
process.stdout.write(event.token)
} else if (event.type === 'done') {
console.log('\nConversation:', event.conversation_id)
console.log('Credits used:', event.credits_used)
} else if (event.type === 'error') {
console.error(event.error)
}
}
}
| Type | Meaning |
|---|---|
token | Partial reply text in token |
action_start | A tool started. Includes actionName. |
action_complete | A tool finished. Includes actionName and success. |
gate_triggered | A safety or policy gate changed the turn. Includes decision and reason. |
done | Stream finished. Includes the same final fields as a non-streaming reply. |
error | Request failed mid-stream. The message is in error. |
Handle any other type safely. A replayed stream sends one done event for the original reply.
Rate Limits#
The Chat API is rate-limited to 60 requests per minute per API key.
Responses include rate-limit headers so you can track usage. When you exceed the limit, the API
returns 429 with retry_after_seconds (and related limit fields). Back off before retrying.
Error Handling#
| Status | Meaning |
|---|---|
| 200 | Success, including an idempotent replay of the same completed turn |
| 400 | Invalid body, or IDEMPOTENCY_KEY_REQUIRED |
| 401 | Invalid or missing API key |
| 402 | Insufficient credits (INSUFFICIENT_CREDITS), with credits_required and credits_remaining when known |
| 403 | The key lacks chat, is outside its agent scope, or the plan does not allow this agent |
| 404 | Agent not found |
| 409 | In-flight duplicate, idempotency conflict, uncertain earlier turn, paused AI interaction, or stale Workflow |
| 429 | Rate limited |
| 500 | Server error |
| 503 | Service temporarily unavailable |
Chat errors are a flat JSON object, not the operation receipt used by other project operations. For example:
{
"error": "Rate limit exceeded. Please slow down.",
"retry_after_seconds": 30,
"limit": 60,
"remaining": 0
}
A 409 body includes a stable code:
| Code | Meaning |
|---|---|
REQUEST_IN_PROGRESS | This key is still processing. Wait and send the same request again. |
IDEMPOTENCY_CONFLICT | This key was already used for a different message. |
OUTCOME_UNCERTAIN | The earlier turn must be reconciled before another attempt. Do not mint a new key. |
AI_REPLY_PAUSED | A Workflow interaction was sent while AI replies are paused. |
WORKFLOW_INTERACTION_STALE | Read the latest turn and select a current button. |
Best Practices#
- Store
conversation_idso multi-turn chats keep context. - Send a new
idempotency_keyfor every new message, and reuse it only for that same request. - Render
presentationonly when it is present, and test custom widgets in the web widget. - Use streaming when you want progressive UI updates.
- Keep keys server-side and rotate them if they leak.
- Watch credits via
credits_used/credits_remainingand your Usage page.