Chat API
Themis exposes a REST API that lets external applications send messages and receive AI responses. The API reuses the same agent pipeline as web and messaging channels, so all skills, tools, and MCP servers available to your space work automatically.
Authentication
Every request requires a per-space API key sent as a Bearer token:
Authorization: Bearer thm_0c53ab18dad0f404...
Managing API Keys
- Go to Space Settings > API
- Click Generate API Key
- Copy the key (use the eye icon to reveal, Copy button to clipboard)
- Use Regenerate to rotate, Revoke to disable
API keys are encrypted at rest and scoped to a single space.
Endpoints
Send a Message
POST /api/v1/conversations
Content-Type: application/json
Authorization: Bearer <api_key>
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
message | string | yes | The message text |
conversation_id | integer | no | Continue an existing conversation |
callback | boolean | no | Enable webhook callback on completion (requires webhook URL in space settings) |
metadata | object | no | Arbitrary metadata stored on the conversation |
Example — new conversation:
curl -X POST https://your-themis.example.com/api/v1/conversations \
-H "Authorization: Bearer thm_..." \
-H "Content-Type: application/json" \
-d '{"message": "Evaluate property at 渋谷区神宮前1-1-1"}'
Example — continue conversation:
curl -X POST https://your-themis.example.com/api/v1/conversations \
-H "Authorization: Bearer thm_..." \
-H "Content-Type: application/json" \
-d '{"message": "What about the revenue projection?", "conversation_id": 325}'
Response (201 Created):
{
"id": 325,
"status": "processing",
"created_at": "2026-03-28T03:12:16Z",
"updated_at": "2026-03-28T03:12:16Z",
"messages": [
{
"id": 1851,
"role": "user",
"content": "Evaluate property at 渋谷区神宮前1-1-1",
"created_at": "2026-03-28T03:12:16Z"
},
{
"id": 1852,
"role": "assistant",
"content": null,
"created_at": "2026-03-28T03:12:16Z",
"processing": true
}
]
}
Poll for Response
GET /api/v1/conversations/:id
Authorization: Bearer <api_key>
Poll this endpoint until status changes from "processing" to "completed".
Response fields:
| Field | Type | Description |
|---|---|---|
id | integer | Conversation ID |
status | string | "processing", "completed", or "input_requested" |
messages | array | All messages in chronological order |
messages[].content | string | Message text (null while processing) |
messages[].images | array | Attached images (if any) |
messages[].files | array | Attached files with filename, content_type, byte_size, url |
messages[].artifacts | array | Generated artifacts (HTML widgets, charts, etc.) |
Example:
curl https://your-themis.example.com/api/v1/conversations/325 \
-H "Authorization: Bearer thm_..."
Send a Message with Streaming
Create a conversation and receive the response as an SSE stream in a single request. Must be enabled per space in Space Settings > API > SSE Streaming.
POST /api/v1/conversations/stream
Content-Type: application/json
Authorization: Bearer <api_key>
Same request body as POST /api/v1/conversations. Returns text/event-stream instead of JSON.
Example:
curl -N -X POST https://your-themis.example.com/api/v1/conversations/stream \
-H "Authorization: Bearer thm_..." \
-H "Content-Type: application/json" \
-d '{"message": "Evaluate property at 渋谷区神宮前1-1-1"}'
Reconnect to Stream
If the SSE connection drops, reconnect to an existing conversation’s stream. Pass last_event_id to resume from where you left off.
GET /api/v1/conversations/:id/stream?last_event_id=42
Authorization: Bearer <api_key>
Accept: text/event-stream
Event types:
| Event | Description | Data |
|---|---|---|
message_start | Agent started processing | conversation_id, message_id |
status | Status update | summary |
thinking | Agent reasoning | summary |
tool_use | Tool invoked | tool (tool name) |
tool_result | Tool returned | tool_use_id, summary, is_error |
content_delta | Text chunk | delta (new text, throttled at 500ms) |
ping | Keepalive (every 15s) | {} |
message_complete | Response finished | message_id, content (preview) |
error | Processing failed | message |
done | Stream ended | {} |
Sample stream:
event: message_start
data: {"conversation_id":325,"message_id":1852}
event: thinking
data: {"summary":"Analyzing the property location and searching for comparable listings..."}
event: tool_use
data: {"tool":"mcp__metabase__execute_query"}
event: tool_result
data: {"summary":"Found 12 comparable properties in Shibuya-ku","is_error":false}
event: content_delta
data: {"delta":"Based on the analysis, this property"}
event: content_delta
data: {"delta":" scores 82/100 with strong revenue potential."}
event: ping
data: {}
event: message_complete
data: {"message_id":1852,"content":"Based on the analysis, this property scores 82/100..."}
event: done
data: {}
Notes:
- Stream timeout is 3 minutes. For longer tasks, reconnect and poll
GET /conversations/:idfor the final result. - Each stream holds a web server thread. Use polling for batch workloads.
- Content deltas are throttled (500ms / 20 chars minimum) to reduce overhead.
Webhook Callback
For long-running tasks, pass "callback": true in the POST body. When the agent completes (or fails), Themis POSTs the result to the webhook URL configured in Space Settings > API.
Setup: Enter a webhook URL (HTTPS only) in Space Settings > API.
Request:
curl -X POST https://your-themis.example.com/api/v1/conversations \
-H "Authorization: Bearer thm_..." \
-H "Content-Type: application/json" \
-d '{"message": "Evaluate this property...", "callback": true}'
Callback payload (POST to your webhook URL):
{
"id": 325,
"status": "completed",
"created_at": "2026-03-28T03:12:16Z",
"updated_at": "2026-03-28T03:15:42Z",
"messages": [
{ "id": 1851, "role": "user", "content": "Evaluate this property..." },
{ "id": 1852, "role": "assistant", "content": "Based on the analysis..." }
]
}
Verification: Each callback includes an X-Themis-Signature header with an HMAC-SHA256 signature of the body, using the space API key as the secret:
import hmac, hashlib
expected = hmac.new(api_key.encode(), body, hashlib.sha256).hexdigest()
assert signature == f"sha256={expected}"
Notes:
- Webhook URL must be HTTPS.
- Retries 3 times on network errors (30s backoff).
- Callbacks fire for both
completedandfailedstatuses. - Disabled by default – only fires when
"callback": trueis in the request.
Error Responses
| Status | Body | Cause |
|---|---|---|
| 400 | {"error": "param is missing or the value is empty: message"} | Missing required field |
| 401 | {"error": "Unauthorized"} | Missing or invalid API key |
| 404 | {"error": "Not found"} | Conversation not found or wrong space |
| 422 | {"error": "..."} | Validation failure |
Typical Integration Flow
1. POST /api/v1/conversations { message: "..." }
→ 201 { id: 325, status: "processing" }
2. Poll GET /api/v1/conversations/325
→ 200 { status: "processing", messages: [...] }
3. Poll again...
→ 200 { status: "completed", messages: [..., { role: "assistant", content: "..." }] }
4. (Optional) Continue with conversation_id
POST /api/v1/conversations { message: "follow up", conversation_id: 325 }
For interactive use cases where real-time feedback matters, use the streaming endpoint:
1. POST /api/v1/conversations/stream { message: "..." }
→ text/event-stream with real-time events until done
For long-running tasks (e.g., property evaluations), use webhook callback:
1. POST /api/v1/conversations { message: "...", callback: true }
→ 201 { id: 325, status: "processing" }
2. Wait for callback POST to your webhook URL
→ { id: 325, status: "completed", messages: [...] }