Type: reference · Imported reference (adopted from the clarity backend developer docs)
Four resources have hand-written handlers instead of the generic entity CRUD layer: connections (data-source config), tag_mappings (dynamic-column tag mapping), collections (filesystem-backed), and attachments (file storage). Plus the introspection endpoints.
All require auth except attachments, which are unauthenticated.
The config store for external data sources (PI / OPC UA / MQTT). Real columns: url, username, password, collectionId, interval_ms, plus a meta_data blob. Unknown body keys auto-add a TEXT column. Row responses flatten meta_data into the top level.
/exactapi/connections (and /connections/add){ "url": "opc.tcp://10.0.0.5:4840", "username": "svc", "password": "secret",
"collectionId": "col_abc", "interval_ms": 5000, "protocol": "opcua" }
Response 201:
{ "id": 3, "url": "opc.tcp://10.0.0.5:4840", "username": "svc", "password": "secret",
"collectionId": "col_abc", "interval_ms": 5000, "protocol": "opcua" }
| Method | Path | Purpose |
|---|---|---|
| GET | /exactapi/connections |
List (array). |
| GET | /exactapi/connections/{id} |
One, or 404 { "error": "Connection not found" }. |
| PUT | /exactapi/connections/{id} |
Update; returns the updated row. |
| DELETE | /exactapi/connections/{id} |
{ "status": "deleted" } or 404. |
| POST | /exactapi/connections/filter |
LoopBack filter over connections (body is a filter object) → array. |
| POST | /exactapi/connections/update/{id} |
Update by path id. |
| POST | /exactapi/connections/update |
Update with id in body (missing → 400 { "error": "Missing id in body for update" }). |
Maps friendly metric names to generated tag ids. Dynamic columns: any unknown body key auto-adds a TEXT column. Responses are column-keyed (no meta_data flatten).
/exactapi/tag_mappings — resolve-or-createmetricName is required (400 otherwise). Reuses an existing row only if every field in the body matches exactly; otherwise inserts. Returns 201 for a new row, 200 for a reused one.
{ "metricName": "steam_temp", "unitsId": "8", "equipmentId": "12", "aggregation": "avg" }
Response 201:
{ "id": 17, "metricName": "steam_temp", "unitsId": "8", "equipmentId": "12",
"aggregation": "avg", "generatedDataTagId": "..." }
generatedDataTagId is what the mapped-tag query/write endpoints resolve to (see Queries → fast_query_tag_mapping and Writes → write_tag_mapping).
| Method | Path | Purpose |
|---|---|---|
| GET | /exactapi/tag_mappings |
List with ?filter=. |
| GET | /exactapi/tag_mappings/{id} |
One, or 404. |
| PUT | /exactapi/tag_mappings/{id} |
Update (400 if no fields, 404 if missing). |
| DELETE | /exactapi/tag_mappings/{id} |
{ "status": "deleted", "id": <id> } or 404. |
A separate resource from create_collection: these routes read/write the on-disk metadata.json grids and auto-provision the matching org/site/unit DB rows.
/exactapi/collections{ "id": "col_abc", "organization": "AcmePower", "site": "Plant1",
"unit": "Boiler1", "grid": "grid0", "tags": ["VTP_G1"],
"descriptions": ["Steam temp"], "intervalMs": 60000 }
Response (metadata appears both nested and flattened):
{
"organization": "AcmePower", "site": "Plant1", "unit": "Boiler1", "grid": "grid0",
"metadata": { "tags": ["VTP_G1"], "descriptions": ["Steam temp"], "id": "col_abc", "interval_ms": 60000 },
"tags": ["VTP_G1"], "descriptions": ["Steam temp"], "id": "col_abc", "interval_ms": 60000
}
organization/site/unit/grid are required (else 400).
| Method | Path | Purpose |
|---|---|---|
| GET | /exactapi/collections |
List, with ?filter= / X-Filter. |
| GET | /exactapi/collections/{id} |
One. |
| PUT | /exactapi/collections/{id} |
Replace. |
| DELETE | /exactapi/collections/{id} |
Cascade: removes files, nulls connections.collectionId, removes tags from scope map → { "status": "deleted" }. |
Filesystem container/file store. No auth. Default containers: tasks, incidents, uploads, mail, pulselogo. Container/file names containing .., /, or \ are rejected (400).
| Method | Path | Purpose |
|---|---|---|
| GET | /exactapi/attachments |
List containers → [ { "name": "tasks" }, … ]. |
| POST | /exactapi/attachments |
Create container { "name": "reports" } (idempotent). |
| GET | /exactapi/attachments/{container} |
{ "name": "<container>" } or 404. |
| DELETE | /exactapi/attachments/{container} |
Recursive → { "status": "deleted" }. |
| GET | /exactapi/attachments/{container}/files |
List files (name, size, atime/mtime/ctime). |
| GET | /exactapi/attachments/{container}/files/{file} |
File details or 404. |
| DELETE | /exactapi/attachments/{container}/files/{file} |
{ "status": "deleted" }. |
| POST | /exactapi/attachments/{container}/upload |
Upload (multipart or raw body). |
| GET | /exactapi/attachments/{container}/download/{file} |
Stream the file (application/octet-stream). |
Upload accepts multipart/form-data or a raw single-file body. The filename is resolved from ?filename= → X-Filename header → Content-Disposition → an auto-generated name. Response { "status": "uploaded", "count": 1 }; no files → 400 { "error": "No files uploaded" }.
| Method | Path | Returns |
|---|---|---|
| GET | /exactapi/_tables |
Array of discovered table names. |
| GET | /exactapi/_schema |
Per-table columns + FK relations. |
| GET | /exactapi/dyn/_schema |
The discovered relation map (belongs_to / has_many). |
// GET /exactapi/_schema (excerpt)
{
"tables": [
{ "name": "units",
"columns": ["orgsId", "siteId", "id", "meta_data"],
"fks": [
{ "column": "orgsId", "references": "orgs", "to_column": "id" },
{ "column": "siteId", "references": "sites", "to_column": "id" }
] }
]
}
connections are consumedAdopted from clarity backend developer docs (
docs/developer/api/special-resources.md), imported reference.
Primary handlers:clarity:backend/src-tauri/src/sqlite_api/mod.rs(connections, tag_mappings, collections, attachments handlers).
Last updated: 2026-07-16 from clarity@bff451d