The chat and search endpoints
Both take a JSON body (or form fields) by POST, answer JSON, and are rate limited per IP address.
POST /chatbot/api/chat
{ "question": "Who painted the field at dusk?", "collection_ids": [3] }
| Field | Required | |
|---|---|---|
question |
yes | Up to 1000 characters. |
collection_ids |
no | Only evidence from these collections. |
{
"answer_html": "<p>Mara Lind painted it <sup><a href=\"#chatbot-source-1\" class=\"chatbot-citation\">[1]</a></sup>.</p>",
"sources": [
{ "citation_num": 1, "title": "Field at Dusk", "url": "https://example.org/items/show/12",
"resource_type": "item", "resource_id": 12, "snippet": "Lind painted the field behind her studio…" }
]
}
answer_html is escaped text in paragraphs, with the citations as links; sources lists the cited sources only.
POST /chatbot/api/search
{ "query": "evening light over fields", "page": 1, "per_page": 10, "collection_ids": [3] }
{
"results": [
{ "resource_type": "item", "resource_id": 12, "title": "Field at Dusk",
"url": "https://example.org/items/show/12", "distance": 0.31, "snippet": "Lind painted the field…" }
],
"total": 47, "page": 1, "per_page": 10
}
distance is the cosine distance of the closest chunk: 0 is identical, 1 unrelated.
Errors
| Status | Body error |
|---|---|
| 400 | The question or query is missing or too long. |
| 403 | Chat or search is turned off, or the chat is for signed-in users only. |
| 405 | Not a POST. |
| 429 | Over the rate limit. |
| 503 | OpenAI or Turso failed, or the plugin is not set up (the detail is in the log, never in the answer). |
The system prompt cannot be sent: it is the one set in the admin.
The scripts
The plugin's views load chatbot.js and semantic-search.js. A theme can use them too:
<div id="ask"></div>
<script src="/plugins/Chatbot/views/shared/javascripts/chatbot.js"></script>
<script>
ChatbotWidget.init({ apiUrl: '/chatbot/api/chat', mode: 'inline', collectionIds: [3] }, document.getElementById('ask'));
</script>
ChatbotWidget.init({apiUrl, mode: 'widget' | 'inline', heading, collectionIds}, element) and SemanticSearch.init({apiUrl, perPage, placeholder, collectionIds}, element). The styles are in css/chatbot.css and css/semantic-search.css; they use --color-primary when the theme defines it.
Storage
In Omeka's database:
| Table | Holds |
|---|---|
chatbot_log |
The log: level, event, message, context (JSON), time (UTC). |
chatbot_rate_limit |
Requests per IP address per minute, kept an hour. |
chatbot_queue |
Records waiting to be indexed. |
In Turso, one table, chunks: resource_type (item, file, collection, simple_page, exhibit_page), resource_id, chunk_id, chunk_text, metadata (JSON: title, collection_ids), url (relative to the site), created_at and embedding (F32_BLOB(n)), with the DISKANN index chunks_vec_idx. The same table as the Omeka S Chatbot module's, with Classic's kinds of records.
Indexing without background jobs
When background jobs cannot run, chatbot.sync_jobs = 1 in config.ini indexes during the request that saves a record (saving is slower: one OpenAI call per chunk), and re-indexing runs during the request that starts it (keep it to small sites, or the request may time out).