Favorites
Endpoints for the aggregate Favorites view and the bulk favorite toggle. Both
live at /api/favorites.
Despite the name, GET /api/favorites is not a list of Favorite rows. It is the
home/landing aggregate: every entity the caller can read across all types
(Datasets, Datasources, Reports, adhoc Queries, Jobs, Assistants, Libraries,
Apps, Integrations, and Toolkits), each decorated with a favorite boolean. The
client filters to favorite: true to render the Favorites view, but the route
returns the full readable set. POST /api/favorites is the bulk toggle that
marks or unmarks entities as favorites for the current User.
A favorite is always scoped to the caller. The owning principal is taken from
the authenticated session (req.auth.credentials.username), never from the
payload, so one User favoriting an entity never affects another User's view.
GET /api/favorites
Read the home aggregate: every entity the caller can read across all types, each decorated with its favorite status.
Authentication: Authenticated session only. There is no permission pre-block
and no license gate. Rows are filtered by each entity type's own read_access,
so a caller never sees an entity they cannot read, even if another User
favorited it.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
teamId | string | - | Narrow the aggregate to rows owned by this team (item.ownerId === teamId). The only accepted query param. |
Response:
A HAL collection under _embedded["inf:favorites"]. Each row carries
objectType, typeName (an i18n display label), image (the type's icon URL),
tags (TagEntity ids), permissions, and a favorite boolean. System Jobs are
excluded, and Apps are de-duplicated so each App appears once.
[
{
"id": "00000000-0000-4000-8000-000000000001",
"slug": "reorder-points",
"name": "Reorder Points",
"description": "Stock thresholds by product",
"createdAt": "2026-01-15T16:20:00.000Z",
"updatedAt": "2026-01-15T16:20:00.000Z",
"dataUpdatedAt": "2026-01-15T16:20:00.000Z",
"shared": false,
"esType": "data",
"esIndex": "docsex.acme.reorder-points.1700000000000",
"ownerId": "admin",
"datasetFieldReportAccess": false,
"username": "admin",
"displayName": "acme Administrator",
"ownerName": "acme Administrator",
"favorite": true,
"permissions": {
"edit": true,
"share": true,
"delete": true,
"write": true,
"addVisual": true,
"deleteVisual": true,
"changeOwner": true,
"copy": true,
"rename": true,
"refresh": true,
"modifyFlows": true,
"replaceFile": true,
"modifySettings": true,
"createDataView": true,
"assignTags": true,
"createFilter": true,
"scriptFields": true,
"setIndex": false
},
"naturalId": "admin:reorder-points",
"objectType": "dataset",
"typeName": "Dataset",
"image": "/images/icons/dataset.svg",
"tags": []
}
]
The rows render under _embedded["inf:favorites"], not a top-level array, and
the set spans every readable type rather than just the caller's favorites.
Filter client-side on favorite: true to build the Favorites view.
The handler returns rows in database order with no cross-type ORDER BY, so
multi-row results are not stably ordered run to run. Sort client-side if you
need a deterministic order.
GET /api/favorites?teamId={team}
Filtering by teamId narrows the aggregate to rows whose ownerId is that team.
In the captured example the seeded Dataset is owned by a User rather than a team,
so the filtered collection is empty, which demonstrates the filter shape.
POST /api/favorites
Bulk favorite toggle for the current User. With favorite: true it upserts a
Favorite row for each supplied id; with favorite: false it destroys the
caller's matching Favorite rows.
Authentication: Authenticated session only. The favorite is always scoped to the caller, whose principal comes from the session and never from the payload.
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
favorite | boolean | Yes | true to mark as favorites, false to remove |
reports | string[] | No | Report ids |
queries | string[] | No | Adhoc Query ids |
datasets | string[] | No | Dataset ids |
datasources | string[] | No | Datasource ids |
jobs | string[] | No | Job ids |
assistants | string[] | No | Assistant ids |
templates | string[] | No | Template ids |
libraries | string[] | No | Library ids |
integrations | string[] | No | Integration ids |
toolkits | string[] | No | Toolkit ids |
apps | string[] | No | App ids |
The payload is stripUnknown, so any non-whitelisted key is dropped. The route
runs in a transaction.
Example Request:
{ "favorite": true, "datasets": ["00000000-0000-4000-8000-000000000001"] }
Response:
Responds 200 with an empty body. This is the Informer convention for a handler
that returns nothing: it is not 201 (there is no Location) and not 204.
Both the mark and the unmark paths answer 200 with an empty body. Marking with
favorite: true is idempotent (the upsert keys are [tenant, <type>Id, principalId]), and removing an id that was not favorited is a no-op 200.
{
"favorite": true,
"datasets": [
"00000000-0000-4000-8000-000000000001"
]
}
POST /api/favorites (remove)
The unset half of the toggle. Sending favorite: false with the same per-type
id arrays destroys the caller's Favorite rows for those ids.
Example Request:
{ "favorite": false, "datasets": ["00000000-0000-4000-8000-000000000001"] }
{
"favorite": false,
"datasets": [
"00000000-0000-4000-8000-000000000001"
]
}
POST /api/favorites (Assistant)
Assistants are favorited through the assistants array, exactly like the other
per-type id arrays.
Example Request:
{ "favorite": true, "assistants": ["00000000-0000-4000-8000-000000000001"] }
{
"favorite": true,
"assistants": [
"00000000-0000-4000-8000-000000000001"
]
}
The 200 response has an empty body and does not echo the favorited row, so
confirm the change took by re-reading GET /api/favorites and checking the
Assistant row's favorite flag.
Per-entity favorite routes
The toggle above is the bulk surface. Each entity type also exposes its own
favorite route (for example PUT and DELETE /api/assistants/\{id\}/favorite),
documented on that entity's page rather than here. Collection-level routes such
as Comments are likewise documented on the owning entity's page.