Skip to content
Updated Sep 24, 2026 by Barča Dvořáková · Owner: analysisactiveapi-a Edit on GitHub

Table View Settings — API Analysis ​

Five endpoints on the tenant API (apps/backend). Three manage the user's own column arrangement for one overview; two are the pilot overviews' exports, which take the arrangement from the request so that what is exported is what the user sees at that moment, saved or not.

The column arrangement endpoints never take a user identifier: the user is always the authenticated one, so there is no way to address another user's preference.

The project has no shared API-conventions document yet, so the envelope shapes are stated once here. Success:

json
{
  "data": {},
  "status": 200,
  "message": "OK",
  "requestId": "018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b21"
}

Error:

json
{
  "errorCode": "ERR_TABLE_VIEW_UNKNOWN",
  "errorMessage": "Unknown table view.",
  "status": 404,
  "requestId": "018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b21"
}

Authentication, tenant resolution and permission failures are answered by the tenant API's shared handling and are not restated per endpoint.


📥 GET /v1/table-views/{viewKey}/columns ​

Returns the overview's column catalogue for the current user, merged with the user's saved arrangement: every column the user may see, in display order, each with its visibility and whether it can be hidden.

Authorization ​

Requires the read permission of the overview the key names — tenant.buildings.read for buildings, tenant.gauges.read for gauges. A user who cannot open the overview cannot read its arrangement.

Request Headers ​

None beyond the tenant API's standard bearer authentication and tenant selection.

Request Body ​

None.

Request Parameters ​

NameInTypeRequiredDefaultDescriptionValidation / NotesDB Mapping
viewKeyPathEnumYes-The overview.A value of Enum - TableViewKey.Suffix of userPreference.key

Request Logic ​

  • Resolves the overview's column catalogue for this user: the columns the overview defines, minus those the user's permissions, the tenant's modules or the tenant's configuration exclude.
  • Reads the user's row, if any:
sql
SELECT value, updated_at
FROM user_preference
WHERE user_id = @currentUserId
  AND key = 'table.' || @viewKey || '.columns';
  • No row → the catalogue's default selection and order, customised: false.
  • A row → reconciled against the catalogue as described in JSON-DAT: userPreference.value, customised: true. The reconciled result is not written back.
  • Writes nothing.

Transactional Operations ​

N/A — read-only.

✅ Success Response (200) ​

json
{
  "data": {
    "viewKey": "gauges",
    "customised": true,
    "columns": [
      { "key": "name",        "mandatory": true,  "visible": true,  "labelKey": "gauge.list.columns.name" },
      { "key": "medium",      "mandatory": false, "visible": true,  "labelKey": "gauge.list.columns.medium" },
      { "key": "building",    "mandatory": false, "visible": true,  "labelKey": "gauge.list.columns.building" },
      { "key": "purpose",     "mandatory": false, "visible": false, "labelKey": "gauge.list.columns.purpose" },
      { "key": "actions",     "mandatory": true,  "visible": true,  "labelKey": "gauge.list.columns.actions" }
    ]
  },
  "status": 200,
  "message": "OK",
  "requestId": "018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b31"
}

Response Data Mapping ​

Response FieldSource / Value
viewKeyEcho of the path parameter
customisedDerived: a userPreference row exists for this user and key
columns[].keyColumn catalogue; order from userPreference.value.columns after reconciliation, else catalogue default order
columns[].mandatoryColumn catalogue
columns[].visibleuserPreference.value.columns[].visible after reconciliation; true for a mandatory column; catalogue default when no row
columns[].labelKeyColumn catalogue — a GUI label key resolved by the frontend

❌ Error Responses ​

404 ERR_TABLE_VIEW_UNKNOWN ​

Thrown when viewKey is not a value of the enum, or is a retired value.

json
{
  "errorCode": "ERR_TABLE_VIEW_UNKNOWN",
  "errorMessage": "Unknown table view.",
  "status": 404,
  "requestId": "018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b31"
}

📝 PUT /v1/table-views/{viewKey}/columns ​

Saves the user's arrangement of one overview — the full ordered list of columns with their visibility — replacing any earlier one.

Authorization ​

Same as the GET: the overview's read permission. Arranging one's own view needs nothing more than being allowed to open it.

Request Headers ​

None beyond the tenant API's standard bearer authentication and tenant selection.

Request Body ​

json
{
  "columns": [
    { "key": "name",         "visible": true },
    { "key": "building",     "visible": true },
    { "key": "medium",       "visible": true },
    { "key": "purpose",      "visible": false },
    { "key": "actions",      "visible": true }
  ]
}
FieldTypeRequiredValidationDB Mapping
columnsArrayYes1–100 items; no duplicate key.userPreference.value.columns
columns[].keyStringYesA column of this overview's catalogue that the user may see.userPreference.value.columns[].key
columns[].visibleBooleanYesfalse is rejected for a mandatory column.userPreference.value.columns[].visible

Request Parameters ​

NameInTypeRequiredDefaultDescriptionValidation / NotesDB Mapping
viewKeyPathEnumYes-The overview.A value of Enum - TableViewKey.Suffix of userPreference.key

Request Logic ​

  • Validates every column against the user's catalogue. A column the user may not see is rejected, not silently dropped: a client that sends one is out of date or tampered with.
  • Stores the arrangement with version: 1. Catalogue columns absent from the request are stored as hidden at the end, so the stored value always covers the whole catalogue as of the save.
  • Upsert on the unique key:
sql
INSERT INTO user_preference (id, tenant_id, user_id, key, value, created_at, updated_at, created_by, updated_by)
VALUES (@id, @tenantId, @currentUserId, 'table.' || @viewKey || '.columns', @value, now(), now(), @currentUserId, @currentUserId)
ON CONFLICT (user_id, key)
DO UPDATE SET value = EXCLUDED.value, updated_at = now(), updated_by = EXCLUDED.updated_by;
  • Returns the reconciled arrangement, the same shape as the GET, so the client renders what the server now holds.

Transactional Operations ​

A single upsert; last write wins. Two tabs of the same user saving at once leave the later arrangement, which is the expected outcome for a personal display setting.

✅ Success Response (200) ​

Same body as GET /v1/table-views/{viewKey}/columns, with customised: true.

Response Data Mapping ​

As for GET /v1/table-views/{viewKey}/columns.

❌ Error Responses ​

400 ERR_TABLE_VIEW_INVALID_COLUMNS ​

Thrown when the body is malformed: empty or over-long list, duplicate key, missing field.

json
{
  "errorCode": "ERR_TABLE_VIEW_INVALID_COLUMNS",
  "errorMessage": "The column list is not valid.",
  "status": 400,
  "requestId": "018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b31"
}

400 ERR_TABLE_VIEW_COLUMN_NOT_AVAILABLE ​

Thrown when a column is not in the user's catalogue for this overview.

json
{
  "errorCode": "ERR_TABLE_VIEW_COLUMN_NOT_AVAILABLE",
  "errorMessage": "Column 'contractPrice' is not available in this view.",
  "status": 400,
  "requestId": "018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b31"
}

400 ERR_TABLE_VIEW_MANDATORY_COLUMN_HIDDEN ​

Thrown when a mandatory column is sent with visible: false.

json
{
  "errorCode": "ERR_TABLE_VIEW_MANDATORY_COLUMN_HIDDEN",
  "errorMessage": "Column 'name' cannot be hidden.",
  "status": 400,
  "requestId": "018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b31"
}

404 ERR_TABLE_VIEW_UNKNOWN ​

As for the GET.


🗑️ DELETE /v1/table-views/{viewKey}/columns ​

Resets the user's arrangement of one overview to the default by removing the saved one.

Authorization ​

Same as the GET.

Request Headers ​

None beyond the tenant API's standard bearer authentication and tenant selection.

Request Body ​

None.

Request Parameters ​

NameInTypeRequiredDefaultDescriptionValidation / NotesDB Mapping
viewKeyPathEnumYes-The overview.A value of Enum - TableViewKey.Suffix of userPreference.key

Request Logic ​

sql
DELETE FROM user_preference
WHERE user_id = @currentUserId
  AND key = 'table.' || @viewKey || '.columns';

Idempotent: resetting a view that was never customised succeeds. Returns the default arrangement, so the client needs no second call.

Transactional Operations ​

A single delete.

✅ Success Response (200) ​

Same body as GET /v1/table-views/{viewKey}/columns, with customised: false.

Response Data Mapping ​

As for GET /v1/table-views/{viewKey}/columns.

❌ Error Responses ​

404 ERR_TABLE_VIEW_UNKNOWN ​

As for the GET.


📤 POST /v1/buildings/export ​

Downloads the building overview as XLSX: the rows matching the current search and filters (or the rows the user selected), with the columns the user currently shows, in their current order.

Authorization ​

Requires permission code tenant.buildings.read.

Request Headers ​

Accept: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, beyond the standard authentication and tenant selection.

Request Body ​

json
{
  "filters": { "type": "building", "sector": "018f9c4d-0000-7a3e-9b77-4f0d3a5e6b21", "q": "škola", "state": "active" },
  "columns": ["name", "address", "city", "sector", "status"],
  "ids": null
}
FieldTypeRequiredValidationDB Mapping
filtersObjectYesSame fields and rules as the GET /v1/buildings list query, without page / pageSize. {} means no filter.The list's filter mapping on building
columnsArray of StringYes1–100 unique keys from the buildings column catalogue the user may see. Columns the catalogue marks as not exported (row actions, display-only values) are ignored.Column catalogue → building attributes
idsArray of UUIDNo1–5 000 unique ids when present. null or absent exports every row matching filters.building.id

Request Parameters ​

None.

Request Logic ​

  • Validates columns against the user's buildings catalogue. A column the user may not see is rejected — the export never widens what the screen shows.
  • Row set: with ids, those ids intersected with filters and with what the user may read (an id the user cannot see, or that no longer matches, is left out without error); without ids, every row matching filters. Never limited to the current page.
  • Counts the row set first; above the export limit (50 000 rows, a configuration value) the request is refused before any row is read.
  • Orders rows as the list does, writes one sheet with a header row of the columns' labels in the requested order, and streams the workbook.
  • Writes nothing.

Transactional Operations ​

N/A — read-only. Count and read run in one read-only transaction so the limit check and the file describe the same data.

✅ Success Response (200) ​

Binary XLSX body; Content-Disposition: attachment; filename="buildings-YYYY-MM-DD.xlsx". Not wrapped in the envelope.

Response Data Mapping ​

Response FieldSource / Value
Sheet header rowLabels of the requested columns, in the user's language, in request order
Sheet data rowsOne per building row in the set; one cell per requested column, formatted as the overview formats it

❌ Error Responses ​

400 ERR_EXPORT_INVALID_REQUEST ​

Thrown when filters, columns or ids break the rules above.

json
{
  "errorCode": "ERR_EXPORT_INVALID_REQUEST",
  "errorMessage": "The export request is not valid.",
  "status": 400,
  "requestId": "018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b31"
}

400 ERR_TABLE_VIEW_COLUMN_NOT_AVAILABLE ​

As for the PUT.

422 ERR_EXPORT_TOO_LARGE ​

Thrown when the row set exceeds the export limit. The message names the count and the limit so the user knows how far to narrow the filter.

json
{
  "errorCode": "ERR_EXPORT_TOO_LARGE",
  "errorMessage": "The export would contain 61 240 rows; the limit is 50 000. Narrow the filter.",
  "status": 422,
  "requestId": "018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b31"
}

📤 POST /v1/gauges/export ​

Downloads the gauge overview as XLSX on the same terms as the building export.

Authorization ​

Requires permission code tenant.gauges.read.

Request Headers ​

Accept: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, beyond the standard authentication and tenant selection.

Request Body ​

json
{
  "filters": { "building": "018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b21", "medium": "electricity", "archived": "false" },
  "columns": ["name", "medium", "building", "purpose"],
  "ids": ["018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b40", "018f9c4d-2c11-7a3e-9b77-4f0d3a5e6b41"]
}
FieldTypeRequiredValidationDB Mapping
filtersObjectYesSame fields and rules as the GET /v1/gauges list query (building, kind, medium, archived, isPartOfBulkPurchase, q), without paging. The building tab sends its building here.The list's filter mapping on gauge
columnsArray of StringYes1–100 unique keys from the gauges column catalogue the user may see. Columns the catalogue marks as not exported are ignored.Column catalogue → gauge attributes and resolved names
idsArray of UUIDNo1–5 000 unique ids when present.gauge.id

Request Parameters ​

None.

Request Logic ​

As for POST /v1/buildings/export, over gauges. Names shown in the overview (building, subscriber, purpose) are resolved in batch for the exported rows only, as the list does, so the export stays free of per-row lookups.

Transactional Operations ​

N/A — read-only; count and read in one read-only transaction.

✅ Success Response (200) ​

Binary XLSX body; Content-Disposition: attachment; filename="gauges-YYYY-MM-DD.xlsx".

Response Data Mapping ​

Response FieldSource / Value
Sheet header rowLabels of the requested columns, in the user's language, in request order
Sheet data rowsOne per gauge row in the set; one cell per requested column, formatted as the overview formats it

❌ Error Responses ​

As for POST /v1/buildings/export: 400 ERR_EXPORT_INVALID_REQUEST, 400 ERR_TABLE_VIEW_COLUMN_NOT_AVAILABLE, 422 ERR_EXPORT_TOO_LARGE.