Saved Views API
A saved view captures how a list is cut: the filter, the search term, the sorting, the column layout, the display mode, the row density and the rows per page. It always belongs to exactly one entity — a ticket view stays a ticket view. Views are either personal or shared with others, can be pinned to the sidebar and show their result count there. They are managed under /api/saved-views.
Which lists have views
Eleven entities have a column schema and therefore saved views. The result counter of a view only answers if the caller may open the underlying list at all — which is why the table names that permission. Where no permission is listed, the list itself has none: there the row-level visibility alone decides which entries somebody sees.
| Entity | Permission for list and counter |
|---|---|
TICKET | tickets.viewAll or tickets.viewOwn |
INCIDENT | incidents.viewAll or incidents.viewOwn |
PROBLEM | problems.viewAll or problems.viewOwn |
CHANGE | changes.viewAll, changes.viewOwn or changes.viewPendingApprovals |
LICENSE | licenses.viewAll or licenses.viewOwn |
ELIBRARY_ITEM | elibrary.view |
COST_CENTER | costCenters.view |
ASSET | no list permission — asset type grants and assignment decide |
CONTRACT | no list permission — without visibility the list stays empty |
KB_ARTICLE | no list permission — article visibility, status and grants decide |
USER | no list permission — with users.viewAll everyone, otherwise only your own entry |
Permissions
| Permission | Allows | Default roles |
|---|---|---|
savedViews.createOwn | Create and duplicate personal views | End user, agent, admin, approver |
savedViews.createAgentGroup | Share views with agent groups | Agent, admin |
savedViews.createOrganization | Share views with roles (organization-wide) and with any active group, even without membership | Admin |
savedViews.deleteShared | Edit and delete shared views of others and see their recipients | Admin |
User account only: All routes under /api/saved-views require a signed-in user. A view is bound to a person — ownership, sharing and pins are meaningless without one. An API key is therefore rejected with 403.
Endpoints
| Method | Endpoint | Description |
|---|---|---|
GET | /api/saved-views | All visible views as { data: [...] }; optionally narrowed to one entity (?entityType=TICKET). System views first, then the most recently changed. |
GET | /api/saved-views/:id | A single view |
POST | /api/saved-views | Create a view (201) |
PATCH | /api/saved-views/:id | Update a view; version is required |
DELETE | /api/saved-views/:id | Delete a view (204, no body) |
POST | /api/saved-views/:id/duplicate | Copy into your own personal view (201); body { name } |
POST | /api/saved-views/:id/pin | Pin to your own sidebar (204) |
POST | /api/saved-views/:id/unpin | Remove the pin (204) |
POST | /api/saved-views/pins/reorder | Set the order of your own pins (204); body { order: [id, ...] } |
GET | /api/saved-views/:id/count | Result count of the view as { count } |
GET | /api/saved-views/share-targets/users | Selectable users as { data, total } |
GET | /api/saved-views/share-targets/agent-groups | Selectable agent groups as { data, total } |
List, detail, create, update and duplicate all answer with the same shape — a client only has to know one response. Pin, unpin, reorder and delete answer with 204 and no body.
Fields of a view
| Field | Type | Description |
|---|---|---|
entityType | Enum | Target list of the view. Required on create and immutable afterwards — a different entity would be a different view. |
name | String (1–255) | Display name. Names need not be unique; two views may share a name. |
description | String? (≤500) | Short description |
icon, color | String? (≤100 / ≤32) | Icon and color for the picker and the sidebar |
filter | Objekt | Filter tree of the view (required). Structure see below. |
search | String? (≤500) | Search term stored together with the view |
sort | Array? | Up to three sort levels: [{ field, direction }] with direction asc or desc |
columns | Array? | Column layout: [{ key, visible, width?, pinned? }], pinned being left or right |
displayMode | Enum? | table, cards, kanban |
density | Enum? | compact, normal, spacious |
perPage | Int? | Rows per page from the fixed scale 10, 25, 50 or 100. null means: no preference, the list keeps its own default. |
scope | Enum | PERSONAL, AGENT_GROUP, ORGANIZATION — default PERSONAL |
sharedWithUsers, sharedWithAgentGroups, sharedWithRoles | String[] | Recipients as IDs. They are always present in the response but only filled for those allowed to see them (see below). |
isSystem | Boolean | Shipped view: visible to every role and immutable |
isOwner, isShared, isPinned, pinOrder | Boolean / Int? | Response only and always from the perspective of the caller: did they create it, is it shared with anybody, have they pinned it and at which position. |
version | Int | Increases with every change and is required on PATCH — saving on a stale state returns 409 instead of overwriting someone else's work. |
columnsVersion | Int | Increases only on layout changes. It lets a list detect that the view layout changed after a personal column tweak and point that out — renaming does not trigger that notice. |
The filter of a view
The filter is a tree of groups and conditions. A group joins its entries with AND or OR, a condition names field, operator and value. Which fields and operators are allowed is decided by the field catalog of the respective entity — the same basis as the filter queries of the list endpoints.
{
"combinator": "AND",
"conditions": [
{ "field": "status", "operator": "in", "value": ["OPEN", "IN_PROGRESS"] },
{ "field": "assignedAgentId", "operator": "isNotNull", "value": null },
{
"combinator": "OR",
"conditions": [
{ "field": "priority", "operator": "eq", "value": "HIGH" },
{ "field": "createdAt", "operator": "relative", "value": "last_7_days" }
]
}
]
}
Create a view
POST /api/saved-views
{
"entityType": "TICKET",
"name": "Offene P1 des Teams",
"description": "Alle offenen Tickets mit Priorität P1",
"icon": "Ticket",
"color": "#F59E0B",
"filter": {
"combinator": "AND",
"conditions": [
{ "field": "status", "operator": "in", "value": ["OPEN", "IN_PROGRESS"] },
{ "field": "priority", "operator": "eq", "value": "HIGH" }
]
},
"sort": [{ "field": "createdAt", "direction": "desc" }],
"perPage": 50,
"displayMode": "table",
"density": "normal",
"scope": "AGENT_GROUP",
"sharedWithAgentGroups": ["clx-group-servicedesk"]
}
Response (201 Created)
{
"id": "clx-view-123",
"entityType": "TICKET",
"name": "Offene P1 des Teams",
"description": "Alle offenen Tickets mit Priorität P1",
"icon": "Ticket",
"color": "#F59E0B",
"filter": { "combinator": "AND", "conditions": [] },
"sort": [{ "field": "createdAt", "direction": "desc" }],
"perPage": 50,
"columns": null,
"displayMode": "table",
"density": "normal",
"search": null,
"scope": "AGENT_GROUP",
"sharedWithUsers": [],
"sharedWithAgentGroups": ["clx-group-servicedesk"],
"sharedWithRoles": [],
"isSystem": false,
"isOwner": true,
"isShared": true,
"isPinned": false,
"pinOrder": null,
"version": 1,
"columnsVersion": 0
}
Visibility and sharing
The visibility decides which kind of recipients a view may carry — it is not a label, it is enforced. Sharing with individual users is additionally possible in every visibility.
| Visibility | Recipients | Required permission |
|---|---|---|
PERSONAL | Only the owner, plus optionally individual users. Group or role recipients are rejected with 400. | savedViews.createOwn |
AGENT_GROUP | Members of the selected agent groups (at least one). Role recipients are rejected with 400. | savedViews.createAgentGroup |
ORGANIZATION | All users of the selected roles (at least one). | savedViews.createOrganization |
Who sees a view is resolved on every request: its owner, everybody in case of a system view, and whoever a share applies to — as a user, through an active group membership or through their own role. Whoever leaves a group or changes role loses the view with the next request.
Who can be selected as a recipient
Selection and validation use the same sets — what the picker endpoint does not offer is not accepted on save either:
- Users: the caller view on users (with users.viewAll everybody, otherwise only their own entry), narrowed to active login accounts — no anonymized, archived or email-only contacts — and without the caller themselves.
- Agent groups: active, non-archived groups; without savedViews.createOrganization only your own active memberships.
- Roles: active roles from the role option list, which savedViews.createOrganization opens.
Both picker endpoints accept q (search inside the target set), ids (comma separated, resolves existing recipients and takes precedence over q) and limit (default 20, at most 50). Besides data the response carries total, so the interface can show that the list is capped.
The target state is what gets checked: The sharing permissions only apply if visibility or recipients actually change. A pure rename therefore stays possible even when a previously set recipient has become invalid — the next sharing change does require a clean list, though. An invalid recipient with the permission in place is 400; if the permission that widens the selection is missing, the API answers 403 without revealing whether the ID exists.
Who may change or delete a view
One single rule applies: the owner, or — on a SHARED view — whoever holds savedViews.deleteShared. The same permission decides whether the recipient lists are delivered filled; everybody else sees them empty and only learns from isShared THAT the view is shared. A personal view of somebody else is not opened by savedViews.deleteShared. System views are immutable: update and delete answer 403 SAVED_VIEW_SYSTEM_IMMUTABLE — the way forward is to duplicate them.
Duplicating
Every visible view can be copied with savedViews.createOwn. The copy belongs to the caller, is always personal and takes over filter, search term and the complete layout — the shares deliberately stay behind, because a copy is a new view and not a second door into somebody else's permissions. The name of the copy comes in the body ({ name }) so it is formed in the language of the interface; the interface suggests "Name (copy)".
Pinning and result counter
Pinned views appear in the sidebar under "My views", across all entities and in a self-defined order. A pin belongs to the individual user: it is invisible to others and causes no update for them. Pinning and unpinning can be repeated without producing an error.
Next to every row stands the result count of the view. It comes from GET /api/saved-views/:id/count and is delivered as { count }, with Cache-Control: private, no-store — the number depends on the rights of the caller and must therefore not be cached anywhere.
- The counter requires the same permission as the list behind it. Whoever may not open the target list receives 403 SAVED_VIEW_ENTITY_FORBIDDEN instead of a number — otherwise the counter would be a way to learn about stock that the list withholds. The interface simply omits the number badge in that case.
- What is counted is what the list shows: the row-level visibility of the caller and the base rules of the list apply — for users, anonymized entries stay out, in the eLibrary archived documents do.
- The number itself may be up to 30 seconds old; any change to the view discards it immediately. The permissions are always checked before the number — so a withdrawn permission takes effect without delay.
Limits
| Limit | Value |
|---|---|
| Rows per page | 10, 25, 50, 100 (fixed scale; other values are 400) |
| Sort levels | 3 |
| Columns per layout | 60 |
| Column width | 40 to 1200 px |
| Conditions per filter group | 50 |
| Nesting of the filter tree | 10 |
| Recipients per share list | 100 |
| Pins per user | 10 |
| Views per reorder call | 50 |
Error Codes
| HTTP | Error Code | Description |
|---|---|---|
| 400 | SAVED_VIEW_INVALID_SCOPE | Visibility and recipients do not match (e.g. a role share on a personal view or a group view without a group) |
| 400 | SAVED_VIEW_SHARE_TARGET_INVALID | A recipient is not in the selectable set — made-up ID, blocked account, archived group, inactive role, or the caller themselves. details names the affected list and the IDs. |
| 400 | SAVED_VIEW_PIN_LIMIT_REACHED | More than 10 pinned views |
| 400 | UNKNOWN_COLUMN_KEY, UNPINNABLE_COLUMN, PIN_LIMIT_EXCEEDED, UNSORTABLE_FIELD, SORT_LEVEL_LIMIT_EXCEEDED, UNKNOWN_DISPLAY_MODE | Layout errors: unknown column, column that cannot be pinned, too many pinned columns, field that cannot be sorted, too many sort levels, unsupported display mode. The same codes apply to the personal list layout. |
| 400 | — | Validation error: perPage outside the scale, unknown query parameter, missing name when duplicating, exceeded lengths |
| 403 | SAVED_VIEW_FORBIDDEN | The view is not shared with the caller, the permission for the chosen visibility is missing, or they may not manage it |
| 403 | SAVED_VIEW_SYSTEM_IMMUTABLE | System views cannot be changed or deleted |
| 403 | SAVED_VIEW_ENTITY_FORBIDDEN | The counter was requested without permission to open the target list |
| 403 | FORBIDDEN | No user context (API key), or the stored filter cannot be applied to the target entity |
| 404 | SAVED_VIEW_NOT_FOUND | The view does not exist |
| 409 | SAVED_VIEW_VERSION_CONFLICT | The view was changed in the meantime; details names the expected and the current version. Reload and reapply the change. |
Live updates
Views run over the real-time channels of the object kind "saved view": create and duplicate report created, every change — including to the sharing — reports updated with the changed fields, deletion reports deleted. Picker, sidebar and counters therefore refresh without reloading; a newly shared view appears at the recipient on its own, a withdrawn one disappears. The object channel of an individual view is only joined by those allowed to see it. Pinning, unpinning and reordering send nothing — they only change your own state.
Related pages
- API Overview — the query syntax of the list endpoints a view builds on
- Permissions & RBAC — roles, permissions and the row-level visibility that applies to views and counters too
- Global Search — the cross-entity search that evaluates the same read permissions
- Real-time & Presence — how the channels that keep views current are built