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

  1. Go to Space Settings > API
  2. Click Generate API Key
  3. Copy the key (use the eye icon to reveal, Copy button to clipboard)
  4. 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:

FieldTypeRequiredDescription
messagestringyesThe message text
conversation_idintegernoContinue an existing conversation
callbackbooleannoEnable webhook callback on completion (requires webhook URL in space settings)
metadataobjectnoArbitrary 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:

FieldTypeDescription
idintegerConversation ID
statusstring"processing", "completed", or "input_requested"
messagesarrayAll messages in chronological order
messages[].contentstringMessage text (null while processing)
messages[].imagesarrayAttached images (if any)
messages[].filesarrayAttached files with filename, content_type, byte_size, url
messages[].artifactsarrayGenerated 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:

EventDescriptionData
message_startAgent started processingconversation_id, message_id
statusStatus updatesummary
thinkingAgent reasoningsummary
tool_useTool invokedtool (tool name)
tool_resultTool returnedtool_use_id, summary, is_error
content_deltaText chunkdelta (new text, throttled at 500ms)
pingKeepalive (every 15s){}
message_completeResponse finishedmessage_id, content (preview)
errorProcessing failedmessage
doneStream 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/:id for 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 completed and failed statuses.
  • Disabled by default – only fires when "callback": true is in the request.

Error Responses

StatusBodyCause
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: [...] }