Skip to main content
POST
Send chat message (streaming or buffered)
Unified endpoint for sending a user message to the Handa Uncle assistant. Supports both JSON responses and server-sent events (SSE) streaming with the same payload.

Authentication & headers

Select your authentication method in the playground above using the security dropdown.
The two authentication methods (bearerAuth and deviceAuth) are mutually exclusive—choose one based on whether the user is registered or a guest. The playground will show the appropriate fields based on your selection.
Streaming is enabled by default. If you want a buffered JSON response instead of SSE, explicitly set X-Stream-Response: false or Stream: false in your request headers.
Every request is also subject to the chat rate limit (20 req/min) and the free-tier message counter enforced by usageLimitMiddleware.

Guest/device auth flow (deviceAuth option)

When using deviceAuth in the playground, provide these headers:
  • x-device-id (required): Unique device identifier
  • x-platform (required): One of android, ios, or web
  • x-user-id (optional): User ID returned from GET /app/launch
  • x-user-email (optional): Email hint for linking
  • x-user-phone (optional): Phone hint in E.164 format
The complete guest experience flow:
  1. Launch – call GET /app/launch with x-device-id and x-platform. The response returns a synthetic userId (e.g. U-813e62a0-...) even when no Auth0 account exists.
  2. Chat as guest – send POST /api/v1/ai/chat with the device headers plus x-user-id returned from the launch step. Requests work immediately until the free counter reaches the configured FREE_MESSAGE_THRESHOLD (default 100).
  3. Hit the limit – on the (threshold + 1) request the API responds with 429 FREE_LIMIT_EXCEEDED and the payload includes error.details.isGuest = true and error.details.requiresSignup = true. Frontends should show the signup modal at this point.
  4. Continue after signup – once the user signs up and receives an Auth0 access token, switch to the bearer header. The userId and conversation history stay intact because the backend links the identity via the stored device metadata.
Device headers are treated with the same chat-rate limiting and audit trails as JWTs. Spoofed or missing headers are rejected with 401 UNAUTHORIZED.

Request body

Attachments must contain either fileId or (data + mimeType + filename). The backend validates file size, ownership, and converts supported media into the AI SDK multimodal format before invoking the LLM.

Masked pre-prompts

Administrators can define reusable “masked” instructions (e.g., persona tweaks or guardrails) via the Management API (POST /api/v1/preprompts, protected by the backend secret). Client applications should fetch the user-facing catalog from GET /api/v1/public/preprompts and send the selected prepromptKey alongside the chat payload. Users only see the friendly label, while the backend silently injects the corresponding hidden prompt into the LLM context.

Guest vs registered usage messaging

usageLimitMiddleware inspects c.get('isGuest') and returns tailored limit errors:
Registered users see "Please upgrade your account..." plus "isGuest": false, "requiresSignup": false. Use the flags to decide whether to show a signup modal or upsell screen.

Responses

Buffered JSON

Streaming (SSE)

Each chunk is sent as data: <json>\n\n. Expect the following shapes:
  • {"type":"token","content":"..."} – incremental tokens.
  • {"type":"toolCall","toolName":"...","args":{...}} – tool invocation start.
  • {"type":"toolResult","toolName":"...","result":{...}} – tool output.
  • {"type":"done","conversationId":"...","messageId":"...","tokenCount":123,"meta":{"timestamp":"...","streaming":true,"durationMs":1400}}
  • {"type":"error","message":"Streaming failed","error":"..."} – terminal failures.
Clients should treat the stream as finished when a done or error event arrives.

Error codes

Retries should respect backoff when rate limited. When FREE_LIMIT_EXCEEDED, surface a signup or upgrade prompt before re-sending traffic.

Example calls

Authenticated user with streaming (default)

Note: No X-Stream-Response header needed—streaming is the default behavior.

Guest/device auth with streaming (default)

Non-streaming (buffered JSON response)

To get a complete JSON response instead of SSE streaming, explicitly disable streaming:
Both calls share the same response envelope; the only difference is how the caller authenticates and whether streaming is explicitly disabled.

Authorizations

Authorization
string
header
required

Auth0 access token for registered users.

Headers

x-device-id
string

Required when Authorization header is omitted. Stable identifier for the calling device.

Minimum string length: 1
x-platform
enum<string>

Client platform. Required with device auth.

Available options:
android,
ios,
web
x-user-id
string

Optional hint for mapping the device to an existing user (returned by GET /app/launch). Strongly recommended so conversations persist once the guest signs up.

x-user-email
string<email>

Optional email hint for device-auth flows.

x-user-phone
string

Optional phone hint in E.164 format for device-auth flows.

Pattern: ^\+?[1-9]\d{7,14}$
X-Stream-Response
enum<string>
default:true

Streaming is ON by default. Set to false to receive buffered JSON instead of SSE streaming.

Available options:
true,
false
Stream
enum<string>
default:true

Alias for X-Stream-Response. Streaming is ON by default. Set to false to disable.

Available options:
true,
false

Body

application/json
message
string
required
Required string length: 1 - 2000
conversationId
string

Existing conversation ID. Omit to start a new thread.

Pattern: ^[0-9a-fA-F]{24}$
model
string

Optional override for the default model.

prepromptKey
string

Optional identifier for a backend-managed pre-prompt. When supplied, the associated masked instructions are injected right after the system prompt.

Required string length: 2 - 64
Pattern: ^[a-zA-Z0-9][a-zA-Z0-9_-]{1,63}$
inputType
enum<string>

Optional hint for how the conversation was initiated. Use 'voice' for voice input, 'file' for file uploads (auto-detected if attachments present), or 'text' for plain messages (default).

Available options:
text,
file,
voice
attachments
object[]

Response

Chat response streamed or buffered.

success
enum<boolean>
required
Available options:
true
data
object
required
meta
object
required