Contents

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

MethodPathSourceDocs
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

always present guaranteed in every response optional may be null
Field Type Description
id string always present

Device or mirror source identifier as defined in topology.json / apimirror-topology.json.

Example: "scales-lab-1", "weather-api"

parameter string always present

The parameter name that was requested.

Examples: "temperature", "weight", "humidity"

value number | null optional

Parsed numeric measurement value. null when the read failed or parsing was not possible.

Example: 23.5

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: "2026-03-31T14:05:32.418"

unit string | null optional

Physical unit of the measured value as defined in the driver or mirror source config.

Examples: "°C", "kg", "%", "hPa"

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 status is ERROR or PARSE_ERROR.

Example: "[Errno 13] could not open port COM3: Access is denied."

values array | null optional

Present only on /multi-read endpoints. Array of {parameter, value, unit} objects — one entry per device parameter, all read from a single hardware query or HTTP call. value is null when the individual parameter could not be parsed.

When values is present, value is always null and parameter / unit are set to the sentinel "MASCARATA_MULTI_READ_MODE_USE_VALUES".

4. ResponseStatus enum

Defined in app/models.py as ResponseStatus(str, Enum). Serializes to its string value in JSON.

ValueMeaningvalue 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.

ValueMeaning
"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:

GET http://localhost:8000/atmo-lab-1/read/temperature
GET http://localhost:8000/atmo-lab-1/read/temperature?appid=my-dashboard
{
  "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
}
{
  "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:

GET http://localhost:8000/weather-api/mirror/temperature
GET http://localhost:8000/weather-api/mirror/temperature?appid=my-dashboard
{
  "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
}
{
  "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.

GET http://localhost:8000/atmo-lab-1/read/multi-read
GET http://localhost:8000/weather-api/mirror/multi-read
{
  "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.

{
  "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."
}
{
  "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 paramTypeDescription
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.

GET http://localhost:8000/atmo-lab-1/read/temperature?appid=my-dashboard

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.

Tokens are prefixed with msc_ and are 52 characters long. They are displayed on the Clients page with copy and regenerate controls.

msc_a3f8c2e1d04b9f6a7e2c1d5b8f3a9e0c2d4b6f8a1e3c5d7

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
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"])
EventEffect
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.
# Unknown token
HTTP 401
{ "detail": "Invalid client token" }

# Valid token but client is blocked
HTTP 403
{ "detail": "Client is blocked" }