Comments & Favorites
Endpoints for commenting on Templates, toggling a Template as a favorite, and managing the caller's per-user Template settings.
These are all per-user routes: they need only an authenticated user, and the
favorite and settings records are scoped to the caller (the favorite by
principalId, the settings by userId). Each route resolves its Template
through template.lookup(params.id) first, so an id the caller cannot see
returns 404.
Comments
Users can comment on a Template for collaboration and discussion. Comments are threaded: each comment links to its replies.
GET /api/templates/{id}/comments
List the Template's top-level comments (those whose parentId is null),
newest first.
Authentication: Required
Pre-blocks: template.lookup(params.id)
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
sort | string | -createdAt | Sort order |
limit | integer | - | Maximum results to return |
Response:
A HAL collection ({ start, count, total } with _embedded["inf:comment"]).
Each comment embeds inf:user (the author) and exposes an inf:comments link
to its replies (./replies). Replies are reached through that link, not this
list.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/comments?sort=-createdAt&limit=0"
}
},
"start": 0,
"count": 1,
"total": 1,
"_embedded": {
"inf:comment": [
{
"_links": {
"self": {
"href": "https://informer.example.com/api/comments/00000000-0000-4000-8000-000000000002"
},
"inf:comments": {
"href": "https://informer.example.com/api/comments/00000000-0000-4000-8000-000000000002/replies"
}
},
"permissions": {
"edit": true,
"delete": true
},
"id": "00000000-0000-4000-8000-000000000002",
"message": "Great template! Could we add a chart showing trends?",
"public": false,
"userId": "admin",
"datasetId": null,
"datasourceId": null,
"reportId": null,
"queryId": null,
"teamId": null,
"jobId": null,
"templateId": "00000000-0000-4000-8000-000000000001",
"assistantId": null,
"libraryId": null,
"mentions": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"parentId": null,
"_embedded": {
"inf:user": {
"_links": {
"self": {
"href": "https://informer.example.com/api/users/admin"
},
"inf:memberships": {
"href": "https://informer.example.com/api/users/admin/memberships"
},
"inf:avatar-upload": {
"href": "https://informer.example.com/api/users/admin/_upload/{uploadId}/avatar-upload",
"templated": true
},
"inf:password": {
"href": "https://informer.example.com/api/users/admin/password"
},
"inf:feed": {
"href": "https://informer.example.com/api/users/admin/feed"
},
"inf:viewed-feed": {
"href": "https://informer.example.com/api/users/admin/_markFeedViewed"
},
"inf:superuser": {
"href": "https://informer.example.com/api/users/admin/superuser"
}
},
"permissions": {
"changeDomain": false,
"changeProfile": true,
"changePassword": true,
"delete": false,
"edit": true,
"reassign": true,
"superuser": true,
"enable": true,
"changeTheme": true,
"lock": true,
"setGlobalPermissions": true,
"manageLogin": true
},
"domain": "local",
"username": "admin",
"displayName": "acme Administrator",
"familyName": "Administrator",
"givenName": "acme",
"middleName": null,
"email": null,
"data": null,
"superuser": true,
"timezone": "America/New_York",
"settings": {
"log": {
"log": false,
"info": false,
"warn": false,
"debug": false,
"error": true,
"remote": false
}
},
"enabled": true,
"source": null,
"sourceId": null,
"lastViewedFeed": null,
"tenant": "acme",
"globalPermissions": {
"ai": false,
"jobCreation": true,
"viewAllTeams": true,
"viewAllUsers": true,
"painlessScriptCreation": true
},
"userFields": {},
"locked": false,
"lockedAt": null,
"loginAttempts": 0,
"passwordSetAt": "2026-01-15T16:20:00.000Z",
"passwordExpiresAt": "2026-01-15T16:20:00.000Z",
"forgotPasswordRequestTime": null,
"mfa": null,
"mfaConfig": {},
"aiMessage": null,
"aiConsent": false,
"hasSharedDatasources": false,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"initials": "NA",
"colorIndex": 4,
"avatarColor": {
"background": "#81C784",
"text": "#1B5E20"
},
"managesPassword": true
}
}
}
]
}
}
POST /api/templates/{id}/comments
Add a comment to a Template.
Authentication: Required
Pre-blocks: template.lookup(params.id)
Request Body:
The payload is stripUnknown, so message is the only field that is kept; any
other field is dropped.
| Field | Type | Required | Description |
|---|---|---|---|
message | string | No | Comment text |
message is not required by the schema (a comment with no message is allowed).
Example Request:
{
"message": "Great template! Could we add a chart showing trends?"
}
Response:
Responds 201 Created with the new comment and a Location header. Creating a
comment also records a templateAddedComment activity. Runs in a database
transaction.
{
"message": "Great template! Could we add a chart showing trends?"
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/comments/00000000-0000-4000-8000-000000000002"
}
},
"permissions": {
"edit": true,
"delete": true
},
"id": "00000000-0000-4000-8000-000000000002",
"public": false,
"tenant": "acme",
"message": "Great template! Could we add a chart showing trends?",
"templateId": "00000000-0000-4000-8000-000000000001",
"userId": "admin",
"updatedAt": "2026-01-15T16:20:00.000Z",
"createdAt": "2026-01-15T16:20:00.000Z",
"mentions": null,
"teamId": null,
"_altid": null,
"datasetId": null,
"datasourceId": null,
"reportId": null,
"queryId": null,
"jobId": null,
"parentId": null,
"assistantId": null,
"libraryId": null
}
Favorites
Users can mark a Template as a favorite for quick access. The favorite is a
presence toggle scoped to the caller (principalId is the caller's username).
GET /api/templates/{id}/favorite
Check whether the caller has favorited this Template.
Authentication: Required
Pre-blocks: template.lookup(params.id)
Response:
Responds 200 with the favorite record when the caller has favorited the
Template. When the caller has not, the route answers 404. There is no
{ favorited: false } body.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/favorite"
}
},
"permissions": {},
"id": "00000000-0000-4000-8000-000000000002",
"principalId": "admin",
"reportId": null,
"datasetId": null,
"queryId": null,
"datasourceId": null,
"jobId": null,
"assistantId": null,
"templateId": "00000000-0000-4000-8000-000000000001",
"libraryId": null,
"appId": null,
"integrationId": null,
"toolkitId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme"
}
PUT /api/templates/{id}/favorite
Add the Template to the caller's favorites.
Authentication: Required
Pre-blocks: template.lookup(params.id)
Response:
Responds 200 with the favorite record (the route upserts and returns the row;
it does not use .created(), so the status is 200, not 201). The toggle is
idempotent: favoriting an already-favorited Template returns the same record.
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/favorite"
}
},
"permissions": {},
"id": "00000000-0000-4000-8000-000000000002",
"tenant": "acme",
"templateId": "00000000-0000-4000-8000-000000000001",
"principalId": "admin",
"reportId": null,
"datasetId": null,
"queryId": null,
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"datasourceId": null,
"jobId": null,
"assistantId": null,
"libraryId": null,
"appId": null,
"integrationId": null,
"toolkitId": null
}
DELETE /api/templates/{id}/favorite
Remove the Template from the caller's favorites.
Authentication: Required
Pre-blocks: template.lookup(params.id)
Response:
Responds 200 with an empty body (Informer convention), not 204.
User Settings
Templates carry per-user settings for customizing the editing experience. The
settings record is scoped to the caller (userId), so each user has an
independent settings row for the same Template.
GET /api/templates/{id}/settings
Get the caller's settings for this Template.
Authentication: Required
Pre-blocks: template.lookup(params.id)
Response:
Responds 200 with the caller's settings row, whose stored values live under
settings. When the caller has no settings row yet, the response is an empty
object {} (not 404). The record exposes an inf:restore link
(./settings/_restore).
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/settings"
},
"inf:restore": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/settings/_restore"
}
},
"id": "00000000-0000-4000-8000-000000000002",
"datasetId": null,
"reportId": null,
"queryId": null,
"templateId": "00000000-0000-4000-8000-000000000001",
"userId": "admin",
"settings": {
"autoSave": false,
"fontSize": 14,
"editorTheme": "light"
},
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"tenant": "acme",
"snapshots": null
}
PUT /api/templates/{id}/settings
Update the caller's settings for this Template.
Authentication: Required
Pre-blocks: template.lookup(params.id)
Request Body:
An object of user-specific settings. The entire payload is stored wholesale as
the row's settings object (creating the row if it does not exist, otherwise
replacing the stored object). It is not merged with the existing settings.
Example Request:
{
"editorTheme": "light",
"autoSave": false,
"fontSize": 14
}
Response:
Responds 200, echoing the payload back.
{
"editorTheme": "light",
"autoSave": false,
"fontSize": 14
}
{
"_links": {
"self": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/settings"
},
"inf:restore": {
"href": "https://informer.example.com/api/templates/00000000-0000-4000-8000-000000000001/settings/_restore"
}
},
"editorTheme": "light",
"autoSave": false,
"fontSize": 14
}
The payload becomes the stored settings object as a whole. To change one
field, send the complete settings object you want to keep, not just the field
that changed.
POST /api/templates/{id}/settings/_restore
Restore the caller's Template settings to defaults by deleting their settings row.
Authentication: Required
Pre-blocks: template.lookup(params.id)
Response:
Responds 200 with an empty body, not 204. After this, GET .../settings
returns {} again.