Sign in

← All schemas

Schema · sensors/reading

Sensor reading

One reading of a sensor as Readings (GET /v1/atlas/sensors/{sensor_id}/readings, raw) and the live signal stream return it (proto iohr.sensors.v1.Reading, JSON field names as the API writes them): when it was produced and received, a number or a state word, labels, and the digest of evidence the producer keeps. A reading carries no content: no text, prompt, document or credential, and the object is closed so a field outside these is refused. A registered sensor's reading holds a value or a state, never both; a built-in sensor's reading (builtin. or mon_) is derived: a monitor's holds its latest latency as the value and its verdict as the state, either may be absent before its first run, and there is no cursor. Fields the API leaves at their default (an empty string, false, an empty object) may be absent. The shared definitions below (state_word, label_key, label_value, labels, evidence_digest, timestamp, sensor_id) are the ones the ingest schema refers to, so a reading sent and a reading read cannot drift. Draft line: it may still be corrected.

Canonical $id, draft
https://schemas.inorbit.hr/sensors/reading/v0.json
01Versions

Each version is a file at its own address. The raw file is plain JSON, served without a sign-in.

02Fields

Fields of v0, the newest version, by path. A name ending in [] is each item of a list. Every field not listed is refused.

  • sensor_idanyrequired

    The sensor it belongs to.

  • produced_atstringrequired

    The producer's clock. Empty only for a built-in or monitor sensor that has not run yet.

    pattern ^$|^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})$

  • received_atstringrequired

    The server's clock when the batch was stamped. Empty only for a monitor that has not run yet.

    pattern ^$|^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})$

  • valuenumberoptional

    A gauge's or counter's value. Absent for a state.

  • statestringoptional

    A state sensor's word; empty or absent for a number.

    pattern ^[a-z0-9_]{0,32}$

  • labelsanyoptional

    Labels the producer sent.

  • evidence_digeststringoptional

    The digest of the producer's evidence; empty or absent when none.

    pattern ^$|^sha256:[0-9a-f]{64}$

  • skewedbooleanoptional

    True when the producer's time and the server's are more than 5 minutes apart (the reading is kept and flagged).

  • cursorstringoptional

    The reading's cursor in the account's order: digits. Empty for a built-in or monitor reading, which is derived and has none.

    pattern ^$|^[0-9]{1,19}$

03Examples

Valid documents with made-up values, as the repository's tests check them.

{
  "sensor_id": "builtin.incidents.open",
  "produced_at": "2026-10-23T14:09:26.123Z",
  "received_at": "2026-10-23T14:09:26.123Z",
  "value": 2,
  "state": "",
  "labels": {},
  "evidence_digest": "",
  "skewed": false,
  "cursor": ""
}

The repository's tests also check 23 documents that this schema must refuse.

04Check a file against it

Replace the file name with yours.

VS Code, JetBrains: settings
"json.schemas": [
  { "fileMatch": ["my-file.json"], "url": "https://schemas.inorbit.hr/sensors/reading/v0.json" }
]
check-jsonschema
check-jsonschema --schemafile https://schemas.inorbit.hr/sensors/reading/v0.json my-file.json
ajv-cli
ajv validate --spec=draft2020 -s <(curl -s https://schemas.inorbit.hr/sensors/reading/v0.json) -d my-file.json
curl
curl -s https://schemas.inorbit.hr/sensors/reading/v0.json
05The schema itself

The raw file of v0, as published.

schemas.inorbit.hr/sensors/reading/v0.json
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://schemas.inorbit.hr/sensors/reading/v0.json",
  "title": "Sensor reading",
  "description": "One reading of a sensor as Readings (GET /v1/atlas/sensors/{sensor_id}/readings, raw) and the live signal stream return it (proto iohr.sensors.v1.Reading, JSON field names as the API writes them): when it was produced and received, a number or a state word, labels, and the digest of evidence the producer keeps. A reading carries no content: no text, prompt, document or credential, and the object is closed so a field outside these is refused. A registered sensor's reading holds a value or a state, never both; a built-in sensor's reading (builtin. or mon_) is derived: a monitor's holds its latest latency as the value and its verdict as the state, either may be absent before its first run, and there is no cursor. Fields the API leaves at their default (an empty string, false, an empty object) may be absent. The shared definitions below (state_word, label_key, label_value, labels, evidence_digest, timestamp, sensor_id) are the ones the ingest schema refers to, so a reading sent and a reading read cannot drift. Draft line: it may still be corrected.",
  "type": "object",
  "additionalProperties": false,
  "required": [
    "sensor_id",
    "produced_at",
    "received_at"
  ],
  "properties": {
    "sensor_id": {
      "description": "The sensor it belongs to.",
      "$ref": "#/$defs/sensor_id"
    },
    "produced_at": {
      "description": "The producer's clock. Empty only for a built-in or monitor sensor that has not run yet.",
      "type": "string",
      "pattern": "^$|^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})$"
    },
    "received_at": {
      "description": "The server's clock when the batch was stamped. Empty only for a monitor that has not run yet.",
      "type": "string",
      "pattern": "^$|^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})$"
    },
    "value": {
      "description": "A gauge's or counter's value. Absent for a state.",
      "type": "number"
    },
    "state": {
      "description": "A state sensor's word; empty or absent for a number.",
      "type": "string",
      "pattern": "^[a-z0-9_]{0,32}$"
    },
    "labels": {
      "description": "Labels the producer sent.",
      "$ref": "#/$defs/labels"
    },
    "evidence_digest": {
      "description": "The digest of the producer's evidence; empty or absent when none.",
      "type": "string",
      "pattern": "^$|^sha256:[0-9a-f]{64}$"
    },
    "skewed": {
      "description": "True when the producer's time and the server's are more than 5 minutes apart (the reading is kept and flagged).",
      "type": "boolean"
    },
    "cursor": {
      "description": "The reading's cursor in the account's order: digits. Empty for a built-in or monitor reading, which is derived and has none.",
      "type": "string",
      "pattern": "^$|^[0-9]{1,19}$"
    }
  },
  "allOf": [
    {
      "if": {
        "properties": {
          "sensor_id": {
            "pattern": "^sen_"
          }
        }
      },
      "then": {
        "properties": {
          "produced_at": {
            "$ref": "#/$defs/timestamp"
          },
          "received_at": {
            "$ref": "#/$defs/timestamp"
          },
          "cursor": {
            "pattern": "^[0-9]{1,19}$"
          }
        },
        "required": [
          "cursor"
        ],
        "oneOf": [
          {
            "required": [
              "value"
            ],
            "properties": {
              "state": {
                "maxLength": 0
              }
            }
          },
          {
            "required": [
              "state"
            ],
            "properties": {
              "state": {
                "$ref": "#/$defs/state_word"
              }
            },
            "not": {
              "required": [
                "value"
              ]
            }
          }
        ]
      }
    }
  ],
  "$defs": {
    "sensor_id": {
      "description": "A sensor's id: sen_ and a ULID for a sensor a producer registered; builtin.<name> for a built-in count (builtin.incidents.open); mon_ and a ULID for a monitor, which is a built-in sensor too. Never a bare UUID: every id carries its prefix.",
      "type": "string",
      "maxLength": 64,
      "oneOf": [
        {
          "pattern": "^sen_[0-9A-HJKMNP-TV-Z]{26}$"
        },
        {
          "pattern": "^mon_[0-9A-HJKMNP-TV-Z]{26}$"
        },
        {
          "pattern": "^builtin\\.[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$"
        }
      ]
    },
    "timestamp": {
      "description": "RFC 3339 with a T and a Z or an offset, at most nine fractional digits. The API writes UTC with milliseconds (2026-10-23T14:09:26.123Z).",
      "type": "string",
      "minLength": 20,
      "maxLength": 40,
      "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})$"
    },
    "state_word": {
      "description": "A state sensor's value: 1 to 32 of a-z, 0-9 and underscore (ok, warn, degraded). A word, never a sentence.",
      "type": "string",
      "pattern": "^[a-z0-9_]{1,32}$"
    },
    "label_key": {
      "description": "A label key: 1 to 32 of a-z, 0-9 and underscore, one of the sensor's label_keys. A key that names a credential (token, secret, password, authorization, api_key, ...) is refused: the service refuses it at registration with the same rule.",
      "type": "string",
      "pattern": "^[a-z0-9_]{1,32}$",
      "not": {
        "pattern": "(^|_)(token|secret|password|passwd|pwd|credentials?|apikey|authorization|bearer|cookie|jwt|(api|private|access|signing|ssh)_key)(_|$)"
      }
    },
    "label_value": {
      "description": "A label's value: 1 to 64 characters, no control characters. The one place a producer's text reaches a reading; it is short and its key is on the sensor's allowlist. Put no secret or sentence here.",
      "type": "string",
      "pattern": "^[^\\u0000-\\u001f\\u007f-\\u009f]{1,64}$"
    },
    "labels": {
      "description": "At most 8 labels, keys from the sensor's label_keys (the schema checks the form; the service checks the list).",
      "type": "object",
      "maxProperties": 8,
      "propertyNames": {
        "$ref": "#/$defs/label_key"
      },
      "additionalProperties": {
        "$ref": "#/$defs/label_value"
      }
    },
    "evidence_digest": {
      "description": "sha256: and 64 lowercase hex characters, of evidence the producer keeps itself. The digest names the evidence; nothing of it travels (ADR 0063).",
      "type": "string",
      "pattern": "^sha256:[0-9a-f]{64}$"
    }
  }
}