Sign in

← All schemas

Schema · sensors/reading

draft

Sensor reading

One reading of a sensor as Readings (GET /v1/atlas/sensors/{sensor_id}/readings, raw) and the live signal stream return it (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.

https://schemas.inorbit.hr/sensors/reading/v0.json
01Versions

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

What changed

One version so far. When a second is published, this is where it is compared with v0, field by field.

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

Documents with made-up values, the same ones our tests check against the schema: the ones it accepts and the ones it must refuse.

6 valid documentsshow
{
  "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": ""
}
23 documents the schema must refuseshow

Each file name says what is wrong with it. Paste one into the check below to see the schema refuse it.

{
  "sensor_id": "00000000-0000-4000-8000-000000000009",
  "produced_at": "2026-10-23T14:09:26.123Z",
  "received_at": "2026-10-23T14:09:26.456Z",
  "value": 182.5,
  "state": "",
  "labels": {
    "volume": "/"
  },
  "evidence_digest": "",
  "skewed": false,
  "cursor": "48213"
}
04Check a file against it

Paste a document to check it against v0 here, or use the address in your editor or CI.

Checked in your browser. The document is not sent anywhere.
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 (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.",
      "type": "string",
      "pattern": "^sha256:[0-9a-f]{64}$"
    }
  }
}