Chat Associations
Endpoints for managing which Toolkits are enabled in a specific Chat. Listing a
Chat's Toolkits resolves the Chat by id and is not AI-gated. Enabling or
disabling a Toolkit is AI-gated (the Toolkit is resolved through toolkit.lookup,
which runs ai.verify first) and additionally requires that the caller own the
Chat.
GET /api/chats/{chatId}/toolkits
List the Toolkits enabled for a Chat.
Authentication: Required
Pre-blocks: chat lookup (Chat.findById -> 404 "Chat not found" when missing)
This route is not AI-gated and performs no ownership check; any authenticated caller who can see the Chat may list its Toolkits.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
chatId | string | Chat id |
Response:
A HAL collection under _embedded["inf:toolkit"]. Each embedded item is the
full Toolkit row enabled for the Chat (not a chat-toolkit join row): the
handler maps each enabled association to its Toolkit and embeds those. There is
no start/count/total envelope.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/chats/00000000-0000-4000-8000-000000000001/toolkits"
}
},
"_embedded": {
"inf:toolkit": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/toolkits/00000000-0000-4000-8000-000000000002"
}
},
"naturalId": "admin:github-integration",
"allowUserInstances": true,
"executionLocation": "server",
"canManageTools": true,
"permissions": {
"write": true,
"edit": true,
"delete": true,
"share": true,
"assignTags": true,
"changeOwner": true
},
"id": "00000000-0000-4000-8000-000000000002",
"name": "GitHub Integration",
"slug": "github-integration",
"type": "custom",
"description": "Access GitHub repositories",
"instructions": null,
"ownerId": "admin",
"shared": false,
"folderId": null,
"config": null,
"integrationId": null,
"source": null,
"sourceId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"integration": null
}
]
}
}
PUT /api/chats/{chatId}/toolkits/{id}
Enable a Toolkit for a Chat. The association is upserted, so re-enabling an already-enabled Toolkit is idempotent.
Authentication: Required
Pre-blocks:
- chat lookup (chat ownership enforced:
chat.usernamemust equal the caller's username, otherwise403; a missing Chat answers404) toolkit.lookup(runsai.verifyfirst, so AI must be enabled on the tenant; an unknown Toolkit id answers404)
The {id} path parameter is the Toolkit's id (a UUID resolved through
toolkit.lookup), not a naturalId.
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
chatId | string | Chat id |
id | string | Toolkit id (UUID) |
Request Body:
A JSON body is required. The Informer GO client sends { "enabled": true }. The
handler ignores the body's contents, but a body-less PUT is rejected by payload
validation.
{ "enabled": true }
Response:
Responds 200 with the persisted ChatToolkit join row
({ tenant, chatId, toolkitId, createdAt, updatedAt }). The handler does not use
.created(), so the status is 200, not 201.
{
"enabled": true
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/chats/00000000-0000-4000-8000-000000000001/toolkits/00000000-0000-4000-8000-000000000002"
}
},
"id": "00000000-0000-4000-8000-000000000003",
"tenant": "acme",
"chatId": "00000000-0000-4000-8000-000000000001",
"toolkitId": "00000000-0000-4000-8000-000000000002",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z"
}
Once a Toolkit is enabled, its tools become available to the AI when it responds in this Chat.
DELETE /api/chats/{chatId}/toolkits/{id}
Disable a Toolkit for a Chat by destroying the association row.
Authentication: Required
Pre-blocks:
- chat lookup (chat ownership enforced:
chat.usernamemust equal the caller's username, otherwise403; a missing Chat answers404) toolkit.lookup(runsai.verifyfirst, so AI must be enabled on the tenant; an unknown Toolkit id answers404)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
chatId | string | Chat id |
id | string | Toolkit id (UUID) |
Response:
Responds 204 No Content with an empty body. This is the literal route behavior:
the handler explicitly calls h.response().code(204), unlike most Informer
DELETE routes which return 200. Disabling a Toolkit that is not currently
enabled still answers 204 (the destroy is a no-op).
Disabling a Toolkit removes its tools from the Chat going forward. Existing Chat history is unchanged.
Use Cases
Project-specific tools
Enable different Toolkits for different Chats. The {id} is the Toolkit's UUID:
# Sales Chat: enable a CRM Toolkit
PUT /api/chats/{salesChatId}/toolkits/{crmToolkitId}
# Dev Chat: enable a GitHub Toolkit
PUT /api/chats/{devChatId}/toolkits/{githubToolkitId}
Dynamic tool access
Enable a Toolkit for the duration of a task, then disable it when done:
// Enable filesystem access for a code-review Chat
await fetch(`/api/chats/${chatId}/toolkits/${filesystemToolkitId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled: true })
});
// Disable it after the review is complete
await fetch(`/api/chats/${chatId}/toolkits/${filesystemToolkitId}`, {
method: 'DELETE'
});
Security boundaries
Limit sensitive tool access to specific Chats:
# Only enable a database-admin Toolkit for an admin Chat
PUT /api/chats/{adminChatId}/toolkits/{databaseAdminToolkitId}
Best Practices
- Minimal access: only enable the Toolkits a Chat actually needs.
- Context-aware: match Toolkits to the Chat's topic.
- Cleanup: disable Toolkits when they are no longer needed.
- Audit: review which Toolkits are enabled for sensitive Chats.