Chat API

Bot Talk API

Last updated: May 11, 2026

Bot Talk API overview

The Bot Talk API lets your servers talk to a specific bot programmatically over HTTPS. It powers integrations with Telegram, Slack, custom apps, automations, or any back-end that needs to send a message and read the bot's reply.

Every Bot Talk key is tied to exactly one bot. The bot is implicit in the key, so the URL paths do not carry a {bot_id} segment.

Base URL

https://api.chatlab.com/aichat

All endpoints in this article are relative to this base URL.

Getting started

  1. Open a bot in the admin app.
  2. Click the API tab at the top of the bot page.
  3. Click + Create API Key, name it, optionally set an IP whitelist and rate limit, then submit.
  4. Copy the full key from the success modal. The plaintext is shown only once.

A key looks like ck_abcdefghijklmnopqrstuvwxyz012345.

Authentication

Send the key in the Authorization header on every request:

Authorization: Bearer ck_abcdefghijklmnopqrstuvwxyz012345

Requests without an Authorization: Bearer ... header return 401 missing_api_key. Unknown keys return 401 invalid_api_key; revoked keys return 401 revoked_api_key. Five invalid attempts in a minute from the same IP trigger a 60-minute block.

Limits

  • Max 5 active Bot Talk keys per bot
  • Max 60 requests per minute per key (token bucket, capacity 60, smooth refill at 1 token per second). Configurable downward at create time - set a lower rateLimitPerMinute and the cap drops, refill rate scales with it.
  • Max message length 4000 characters
  • Concurrent SSE streams per key: 10 on Standard, 30 on Premium

Endpoints

POST /v1/chat

Send a message to the bot and receive the full reply in a single JSON response.

Request body

{
  "message": "Hi, can you help me track my order?",
  "conversationId": "550e8400-e29b-41d4-a716-446655440000",
  "metadata": {"userEmail": "alice@example.com"}
}
  • message - required string, max 4000 chars.
  • conversationId - optional UUID. Omit for a new conversation; reuse the value the server returned previously to append to an existing one.
  • metadata - optional object: {source, userName, userEmail, userPhone}. userEmail is also used to derive the internal client id.

Response body (200)

{
  "message": {"role": "assistant", "content": "Sure, what's your order number?"},
  "conversationId": "550e8400-e29b-41d4-a716-446655440000",
  "model": "gpt-4o-mini",
  "usage": {"creditsUsed": 2},
  "actions": []
}
  • message.role is always "assistant"; message.content is the full reply.
  • model is the model id the bot ran on for this call.
  • usage.creditsUsed accounts for the user message + bot reply (typically 2), plus one extra credit per executed tool call.
  • actions lists tool executions that ran during this turn. Today only function_call entries are emitted (with type, name, status: "completed").

POST /v1/chat/stream

Streaming variant of /v1/chat. Returns Content-Type: text/event-stream with Server-Sent Events.

Request body

Same shape as POST /v1/chat. The stream flag in the body is not required - using this path is what triggers SSE.

Response body (SSE events)

  • message.delta - content fragment { "content": "..." }. Multiple of these stream as tokens arrive.
  • action - tool execution { "type", "name", "status": "executing" }. Emitted when the bot triggers a function call.
  • message.done - terminal event with conversationId, model, usage, and (if any) the completed actions list.
  • error - sent if generation fails; the stream terminates afterward.

Keep-alive SSE comments (:keepalive) are sent every 15 seconds during long generations. Server-side timeouts: 30 seconds for the first token, 120 seconds total per stream.

Curl example

curl -N -X POST https://api.chatlab.com/aichat/v1/chat/stream \
  -H "Authorization: Bearer ck_..." \
  -H "Content-Type: application/json" \
  -d '{"message":"hello"}'

GET /v1/conversations

List conversations for the bot bound to your key, across all sources (widget, WhatsApp, API, etc).

Query parameters

  • limit - 1-100, default 20. Values outside the range are clamped.
  • cursor - opaque value returned in nextCursor on the previous page. Omit for the first page.

Response body (200)

{
  "data": [
    {
      "conversationId": "550e8400-e29b-41d4-a716-446655440000",
      "createdAt": "2026-05-10T14:11:02Z",
      "lastMessageAt": "2026-05-10T14:12:34Z",
      "messageCount": 6,
      "lastMessagePreview": "Thanks for the help."
    }
  ],
  "hasMore": false,
  "nextCursor": null
}
  • lastMessagePreview is truncated to 100 characters with a ... suffix.
  • nextCursor is null on the last page; pass it as cursor to fetch the next.

GET /v1/conversations/{conversation_id}

Full conversation history.

Response body (200)

{
  "conversationId": "550e8400-e29b-41d4-a716-446655440000",
  "createdAt": "2026-05-10T14:11:02Z",
  "lastMessageAt": "2026-05-10T14:12:34Z",
  "messageCount": 6,
  "messages": [
    {"role": "user", "content": "Hi", "createdAt": "2026-05-10T14:11:02Z"},
    {"role": "assistant", "content": "Hello! How can I help?", "createdAt": "2026-05-10T14:11:03Z"}
  ]
}

Only user and assistant roles are returned; internal system and tool messages are filtered out. Returns 404 not_found_error if the conversation does not belong to the bot bound to your key.

To inspect bot configuration, use the Management API endpoint GET /v1/management/bots/{bot_id} with a Management key.

Rate limit headers

Responses that reach the rate-limit stage (i.e. auth and IP whitelist passed) include:

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1715430000
  • X-RateLimit-Limit - the per-key cap actually applied to this call (60 by default, or your configured rateLimitPerMinute if lower).
  • X-RateLimit-Remaining - tokens left in the bucket right after this call.
  • X-RateLimit-Reset - Unix epoch seconds at which the next token becomes available (not a full bucket reset; the bucket refills continuously). When the bucket is full, this is the current time.

On 429 rate_limit_exceeded responses, Retry-After is also set, expressed in whole seconds until at least one token frees up.

Pre-auth errors (401 missing_api_key, 401 invalid_api_key, 403 ip_blocked) and 403 ip_not_whitelisted do not carry the X-RateLimit-* headers - the limiter is only consulted after authentication and IP checks succeed.

Error format

All errors share a single envelope:

{
  "error": {
    "type": "rate_limit_error",
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Try again in 12 seconds.",
    "param": null
  }
}

Common HTTP codes:

  • 400 invalid_request_error - malformed input (codes: invalid_parameter, unsupported_media_type)
  • 401 authentication_error - missing key (missing_api_key), unknown key (invalid_api_key), or revoked key (revoked_api_key)
  • 402 quota_exceeded_error - message credits exhausted
  • 403 permission_error - IP blocked (ip_blocked), IP not in the key's whitelist (ip_not_whitelisted), or key type does not allow this endpoint (key_type_not_allowed, insufficient_permissions)
  • 404 not_found_error - conversation does not belong to this bot
  • 405 invalid_request_error (method_not_allowed) - wrong HTTP verb on the URL
  • 429 rate_limit_error - too many requests (rate_limit_exceeded) or too many concurrent streams (concurrent_streams_exceeded)
  • 500 api_error - internal error

Related

For account-level operations (creating bots, updating bots, fetching usage), see Management API.