Contents
- 1. Overview
- 2. Endpoints that return this model
- 3. Response fields
- 4. ResponseStatus enum
- 5. ResponseSource enum
- 6. Hardware read — example
- 7. API Mirror read — example
- 8. Multi-read — example
- 9. Error response — example
- 10. Client identification (&appid)
- 11. HTTP 403 — Client blocked
- 12. Bearer token authentication
1. Overview
Every read endpoint in Mascarata — whether reading from a serial hardware device or a mirrored external API — returns a single JSON object called MascarataResponse.
This is the standard contract between Mascarata and any client that consumes device data. The shape never changes regardless of the data source. Clients can always rely on id, parameter, status, and timestamp being present.
status and source are typed enums — their possible values are fixed and documented below.
2. Endpoints that return this model
| Method | Path | Source | Docs |
|---|---|---|---|
| GET | /{device_id}/read/{parameter}optional: ?appid= |
Hardware serial device | Swagger |
| GET | /{source_id}/mirror/{parameter}optional: ?appid= |
Mirrored external REST API | Swagger |
| GET | /{device_id}/read/multi-read |
Hardware device — all parameters in one shot | — |
| GET | /{source_id}/mirror/multi-read |
API Mirror — all parameters in one HTTP call | — |
Multi-read endpoints are only available for devices/sources where all parameters share the same write command (one query → all values). They are listed in the Topology and API Mirrors pages for applicable entries.
3. Response fields
| Field | Type | Description | |
|---|---|---|---|
id |
string | always present |
Device or mirror source identifier as defined in Example: |
parameter |
string | always present |
The parameter name that was requested. Examples: |
value |
number | null | optional |
Parsed numeric measurement value. Example: |
status |
ResponseStatus | always present |
Result of the read operation. Typed enum — see ResponseStatus below. |
timestamp |
string | always present |
ISO-8601 timestamp of when the measurement was taken or served from cache. Example: |
unit |
string | null | optional |
Physical unit of the measured value as defined in the driver or mirror source config. Examples: |
raw_response |
string | null | optional |
Raw bytes or body received before parsing. Useful for debugging. STRING protocol: UTF-8 string. BINARY/MODBUS_RTU: uppercase HEX. API Mirror: first 2000 chars of the HTTP response body. |
source |
ResponseSource | null | optional |
Indicates where the data originated. Typed enum — see ResponseSource below. |
message |
string | null | optional |
Human-readable error description. Present only when Example: |
values |
array | null | optional |
Present only on When |
4. ResponseStatus enum
Defined in app/models.py as ResponseStatus(str, Enum). Serializes to its string value in JSON.
| Value | Meaning | value is null? |
|---|---|---|
"OK" |
Data was read and parsed successfully. The value field is reliable. |
No |
"UNSTABLE" |
The device responded, but the parsed value did not pass the driver's validator pattern (e.g. scale is still settling). | No |
"PARSE_ERROR" |
The device responded, but the response could not be parsed into a numeric value. Check raw_response for the raw data. |
Yes |
"ERROR" |
Communication failed — serial port could not be opened, HTTP request failed, or an exception was thrown. Check message for the error detail. |
Yes |
"NOT_SUPPORTED" |
The requested command is not configured in the device driver. Returned only by the /{device_id}/command endpoint. The response includes an available_write_commands list of valid commands for that device. |
Yes |
5. ResponseSource enum
Defined in app/models.py as ResponseSource(str, Enum). Serializes to its string value in JSON.
| Value | Meaning |
|---|---|
"hardware" |
Data was read fresh from the physical device on a real COM port. |
"cache" |
Data was served from the in-memory TTL cache (hardware device). No serial communication was performed. TTL is derived from the driver's connection timeout setting. |
"mock" |
Data came from the virtual port simulator (port is set to MOCK in topology). Used in development without physical hardware. |
"api-mirror" |
Data was fetched fresh from a mirrored external REST API source. |
"cache:api-mirror" |
Data was served from the API mirror's in-memory cache. No HTTP request was made. TTL is controlled by the source's poll_interval_ms setting. |
6. Hardware read — example
Reading temperature from a serial device named atmo-lab-1:
Request
GET http://localhost:8000/atmo-lab-1/read/temperature GET http://localhost:8000/atmo-lab-1/read/temperature?appid=my-dashboard
Response — 200 OK (fresh from hardware)
{
"id": "atmo-lab-1",
"parameter": "temperature",
"value": 23.5,
"status": "OK",
"timestamp": "2026-03-31T14:05:32.418",
"unit": "°C",
"raw_response": "T=23.5 H=48.1 P=1013.2",
"source": "hardware",
"message": null
}
Same request — served from TTL cache
{
"id": "atmo-lab-1",
"parameter": "temperature",
"value": 23.5,
"status": "OK",
"timestamp": "2026-03-31T14:05:32.418",
"unit": "°C",
"raw_response": "T=23.5 H=48.1 P=1013.2",
"source": "cache",
"message": null
}
7. API Mirror read — example
API Mirror sources are external REST APIs that Mascarata polls on your behalf and exposes via the same MascarataResponse format. Configure them on the API Mirrors page.
Reading temperature from a mirror source named weather-api:
Request
GET http://localhost:8000/weather-api/mirror/temperature GET http://localhost:8000/weather-api/mirror/temperature?appid=my-dashboard
Response — 200 OK (fresh fetch from external API)
{
"id": "weather-api",
"parameter": "temperature",
"value": 18.2,
"status": "OK",
"timestamp": "2026-03-31T14:10:05.001",
"unit": "°C",
"raw_response": "{\"main\":{\"temp\":18.2,\"humidity\":61},\"wind\":{\"speed\":4.1}}",
"source": "api-mirror",
"message": null
}
Same request — served from mirror cache (within poll_interval_ms)
{
"id": "weather-api",
"parameter": "temperature",
"value": 18.2,
"status": "OK",
"timestamp": "2026-03-31T14:10:05.001",
"unit": "°C",
"raw_response": "{\"main\":{\"temp\":18.2,\"humidity\":61},\"wind\":{\"speed\":4.1}}",
"source": "cache:api-mirror",
"message": null
}
The only difference between a hardware response and a mirror response is the source field value and the format of raw_response (JSON text instead of serial bytes). Client code that checks status == "OK" works identically for both.
8. Multi-read — example
Devices (or API Mirror sources) where all parameters share the same write command expose an additional /multi-read endpoint. One hardware query (or one HTTP call) returns all parameters at once.
Eligible devices are marked in the Topology table. Eligible API mirrors are listed on the API Mirrors page.
Request
GET http://localhost:8000/atmo-lab-1/read/multi-read GET http://localhost:8000/weather-api/mirror/multi-read
Response — 200 OK
{
"id": "atmo-lab-1",
"parameter": "MASCARATA_MULTI_READ_MODE_USE_VALUES",
"value": null,
"values": [
{ "parameter": "temperature", "value": 22.16, "unit": "CELSIUS" },
{ "parameter": "relative_humidity", "value": 55.12, "unit": "%" },
{ "parameter": "pressure", "value": 1003.23, "unit": "hPa" }
],
"status": "OK",
"timestamp": "2026-04-01T10:19:14.402596",
"unit": "MASCARATA_MULTI_READ_MODE_USE_VALUES",
"raw_response": "T=22.16 H=55.12 P=1003.23",
"source": "hardware",
"message": null
}
The overall status reflects the worst status across all parameters — if one parameter fails to parse, the overall status becomes PARSE_ERROR while other parameters' values are still returned.
9. Error response — example
When a device is unreachable, a serial port error occurs, or an external API request fails, the endpoint still returns HTTP 200 with an error status in the body — never a 5xx. This lets clients process partial data streams without breaking on individual failures.
Hardware error
{
"id": "atmo-lab-1",
"parameter": "temperature",
"value": null,
"status": "ERROR",
"timestamp": "2026-03-31T14:07:11.002",
"unit": null,
"raw_response": null,
"source": null,
"message": "[Errno 13] could not open port COM3: Access is denied."
}
API Mirror error (upstream returned 401)
{
"id": "weather-api",
"parameter": "temperature",
"value": null,
"status": "ERROR",
"timestamp": "2026-03-31T14:07:55.310",
"unit": "°C",
"raw_response": "{\"cod\":401,\"message\":\"Invalid API key.\"}",
"source": "api-mirror",
"message": "HTTP 401"
}
Recommended client pattern — works for both hardware and mirror endpoints:
# Python example import httpx # Hardware device resp = httpx.get("http://localhost:8000/atmo-lab-1/read/temperature", params={"appid": "my-dashboard"}).json() # API Mirror source — same pattern resp = httpx.get("http://localhost:8000/weather-api/mirror/temperature", params={"appid": "my-dashboard"}).json() if resp["status"] == "OK": print(resp["value"], resp["unit"]) else: print("Read failed:", resp["status"], resp.get("message"))
10. Client identification (&appid)
Mascarata tracks every external client that calls a read endpoint and records them in the local database. Each client is identified by a UID.
| Query param | Type | Description |
|---|---|---|
appid |
optional string |
A stable identifier for the calling web application (e.g. domain, app name). Used as the client UID directly. If omitted, UID is auto-derived as the first 12 hex characters of SHA-256(client IP). |
Examples:
# Without appid — UID derived from IP GET http://localhost:8000/atmo-lab-1/read/temperature # With appid — UID is "my-dashboard" GET http://localhost:8000/atmo-lab-1/read/temperature?appid=my-dashboard
Clients are visible on the Clients page and in the Visual Map. New clients are blocked by default — the operator must explicitly allow them before data is returned. Once allowed, a Bearer token is generated for the client and can be used instead of IP-based identification — see section 12.
11. HTTP 403 — Client blocked
If a client's UID has not been allowed on the Clients page, every read request returns HTTP 403 — the device or mirror handler is never called.
Request
GET http://localhost:8000/atmo-lab-1/read/temperature?appid=my-dashboard
Response — 403 Forbidden
When appid is not provided, the UID is auto-derived from IP and the hint is included:
{
"detail": "Client blocked. Allow access for client '12ca17b49af2' from the Clients page. Recommendation: pass a human-readable name via the [appid] query parameter (e.g. ?appid=my-dashboard)."
}
When appid is provided, the message uses it directly (no hint):
{
"detail": "Client blocked. Allow access for client 'my-dashboard' from the Clients page."
}
To allow a client, go to the Clients page and click Allow next to the client's UID, or use the REST API:
PATCH http://localhost:8000/api/clients/my-dashboard/allow
12. Bearer token authentication
Once a client is allowed on the Clients page, a unique API token is generated for it. The token can be used in the Authorization header as an alternative to IP-based identification — it works from any network, does not depend on IP fingerprinting, and is the recommended approach for production integrations.
Token format
Tokens are prefixed with msc_ and are 52 characters long. They are displayed on the Clients page with copy and regenerate controls.
msc_a3f8c2e1d04b9f6a7e2c1d5b8f3a9e0c2d4b6f8a1e3c5d7
Usage
Pass the token in the standard Authorization: Bearer header. The appid query parameter is not needed when using a token — the client is identified by the token itself.
GET http://localhost:8000/atmo-lab-1/read/temperature Authorization: Bearer msc_a3f8c2e1d04b9f6a7e2c1d5b8f3a9e0c2d4b6f8a1e3c5d7
Python example
import requests TOKEN = "msc_a3f8c2e1d04b9f6a7e2c1d5b8f3a9e0c2d4b6f8a1e3c5d7" headers = {"Authorization": f"Bearer {TOKEN}"} resp = requests.get( "http://localhost:8000/atmo-lab-1/read/temperature", headers=headers, ).json() if resp["status"] == "OK": print(resp["value"], resp["unit"])
Token lifecycle
| Event | Effect |
|---|---|
| Client allowed in UI | Token is generated automatically. |
| Client blocked in UI | Requests with the token return HTTP 403. The token is preserved and becomes active again if the client is re-allowed. |
| Token regenerated | The old token is immediately invalidated. Copy the new token from the Clients page before closing. |
| Client deleted | The token is permanently revoked. |
Error responses
# Unknown token HTTP 401 { "detail": "Invalid client token" } # Valid token but client is blocked HTTP 403 { "detail": "Client is blocked" }