Core concepts

You only ever push one thing: events. Everything else — episodes, durations, habits, findings — is derived from them. This page explains the vocabulary, so the endpoint reference reads as a set of obvious choices rather than a list of fields.

The chain

What happens to an event

event → mapping → entity resolution → definition selection
      → business key → derivation (episode | measurement)
      → rule evaluation → finding

Every step is idempotent and replayable: run it again on the same input and you get the same state, with nothing duplicated. That property is what makes retries safe, backfills reproducible, and corrections cheap.

Event

A fact that happened at a point in time, in one of your systems. It carries an identifier that is stable on your side (source_event_id), a type, an occurred_at with a UTC offset, and, optionally, a payload you are free to shape.

sinio.ai works on occurred_at, never on the time it received the event. Out-of-order delivery is normal and expected: an event arriving with a timestamp earlier than episodes already computed triggers a recomputation of the affected key, not an error.

Source

One of your tools — an access-control system, a maintenance software — created in the project with its own API keys. The API, the SDKs and webhook URLs are only channels into it: a project key always writes into its source.

Identifiers and event types are scoped by source, on purpose. Two of your systems can use the same identifier or the same raw code for different things (badge 2 is not asset 2, closed in a ticketing tool is not closed in an access control system) and sinio.ai will not conflate them.

When two of your tools do mean the same thing — technician 2 in a maintenance tool, E123 in your HR system — keep sending each its own identifier and link them once in the console, under Settings › Identifiers: attach a pair by hand, or set a matching rule when both tools write the same external_id for one kind of entity, for the identifiers already there and those to come. Episodes, habits and reports then count them as one; detaching splits them again. Linking is always a decision made in the console: sinio.ai never infers it, and the API has no call for it.

Entity and the four roles

An event can point at up to four structural roles, in the fixed roles envelope — any other key there is refused:

  • Name
    device
    Type
    string
    Description

    What produced or recorded the fact — a reader, a sensor, a terminal.

  • Name
    subject
    Type
    string
    Description

    Who or what the fact is about — a person, a badge, a vehicle, an asset.

  • Name
    location
    Type
    string
    Description

    Where it happened, as a location external_id.

  • Name
    actor
    Type
    string
    Description

    Who performed it, when that differs from the subject — a technician closing someone else's work order.

Everything else stays in payload, free-form. A ticket number, a firmware version, a temperature: sinio.ai stores it whole and reads from it only what the type's published interpretation tells it to.

What a role's identifier is — a person, a badge, a piece of equipment, the device speaking about itself — is not sent with each event. It is said once, when the event type is classified, for that type and source: a badge reader's subject is a badge, a picking scan's subject a person. It cannot change once events of that type are accepted. A device's model (its subtype) belongs to the device itself: declare it with POST /v1/entities or name it in the console.

Entities and business references

Put in roles only the lasting things you would declare once: devices, people, equipment, badges. An order, a return, a ticket, a parcel or a batch number is a business reference: it lives for one piece of work, then is never seen again. Send it in payload:

{
  "type": "picking_started",
  "roles": { "device": "scanner-3", "actor": "picker-12" },
  "payload": { "order": "CMD-4410" }
}

A process then groups its steps by that reference (keyed_by: business_reference, key_reference: order), and each event type says where its payload carries it (interpretation.references: { "order": "$.order" }) — every step of order CMD-4410 joins the same episode, whoever scanned it, even when two tools write it in different fields. Sent as a subject instead, each order would become an undeclared entity to review, one per order.

Event type

The semantics of type. A type belongs to a domain (attendance, intervention, telemetry, incident, satisfaction, traceability), has a nature (step, measure, annotation), and may carry an interpretation saying what each of its roles stands for and where its payload carries each business reference. The values it carries are read by its source's readings, set in the console (Measurements).

A type you have not declared is not an error. The event is stored with status pending_classification and counted separately in the response: the payload is perfectly valid, only its meaning is not known yet. Event types are configured in the sinio.ai console: once a type is given a meaning there, the waiting events are derived with it.

Episode

The unit of meaning. An episode is a sequence of steps sharing a business key — an attendance from badge in to badge out, an intervention from dispatch to sign-off, a round from first to last checkpoint.

An episode_definition — a process, in the console, where it is configured — says which event types are its steps, how the key is composed (by subject, by subject and location, by location, by device, or by a business reference in the payload), how it closes, and how long it may stay open. sinio.ai selects the most specific active definition matching the event.

An episode always ends up in one of these states:

  • Name
    open
    Type
    status
    Description

    Started, not closed yet.

  • Name
    closed
    Type
    status
    Description

    Complete, with a computed duration.

  • Name
    expired
    Type
    status
    Description

    Stayed open past its max_open window. Raises a missing_step finding.

  • Name
    orphan_close
    Type
    status
    Description

    A closing step arrived with no episode open for that key. No phantom episode is ever created retroactively.

  • Name
    orphan_open
    Type
    status
    Description

    An opening step that never found its pair.

  • Name
    out_of_order
    Type
    status
    Description

    The closing step predates the opening one. Duration stays null; the real timestamps survive on the steps.

  • Name
    ambiguous
    Type
    status
    Description

    Several definitions matched with equal specificity. Resolved deterministically, and reported as a configuration finding.

A duplicate step never creates a second episode. Badge twice at 07:58 and 08:01 and you get one attendance plus a duplicate_step flag — not two attendances, one of which would expire unclosed and poison every duration statistic downstream.

Presence days

For an attendance process, each passage (badge in → badge out) stays an episode, and sinio.ai also gathers a badge holder's passages into a presence day. A lunch break then no longer reads as a second arrival: habits, durations and daily volumes are judged on the day. Nothing changes in what you send — the day is derived, and set up in the console, per population (the subtype of the badge holder):

  • per passage or per day, and the hour a day starts — a night from 20:55 to 05:05 is one day, the evening's;
  • a maximum interruption: past it, the next entry starts a new day.

The gaps between the passages of a day are absences. An absence is measured when both its exit and its return were observed, and of unknown length when one of them is missing — never zero, and never counted in a sum.

An absence can be qualified as a lunch_break or a break, in this order of precedence:

  1. The reason your event declares. Say where the payload carries it, as a business reference named absence_reason on the event type (interpretation.references: { "absence_reason": "$.reason" }). Send your own raw values — LUNCH, R12, PAUSE_REPAS — and map them once in the console.
  2. The time of the exit, against time slots set in the console.

If your system sends its own pause start and pause end events between entry and exit, send them as ordinary events with their own types:

{ "type": "pause_start", "occurred_at": "2026-09-25T12:02:00+02:00",
  "roles": { "subject": "badge-14682", "device": "kiosk-2" } }

The process declares, in the console, which type starts and which ends a pause. They are never steps: they open and close no passage. sinio.ai pairs them within the day, merges a declared pause with the badging gap it overlaps, and deducts it from the day's net presence. A pause boundary outside any presence is reported as pause_outside_presence.

Finding

What sinio.ai has to tell you. A finding carries a category, a kind, a severity decided by the engine, and an explanation shaped as observed versus expected.

  • Name
    operational
    Type
    category
    Description

    Something about the field: missing_step, sla_breach, duration_exceeded, volume_out_of_range, device_silent, late_close, the measurement findings (measure_limit_crossed, measure_off_habit, measure_fleet_outlier, projected_depletion, expected_measure_missing, state_too_long — see Measurements), the pause surveillances (pause_duration_exceeded, pause_count_exceeded, pause_total_exceeded), behavioural anomalies against a subject's own habits.

  • Name
    configuration
    Type
    category
    Description

    Something about the setup: undeclared_entity, undeclared_subkind, overlapping_assignment, ambiguous_definition.

  • Name
    data_quality
    Type
    category
    Description

    Something about the data: unmapped_event_type, source_conflict, pause_outside_presence, measure_value_impossible.

  • Name
    security
    Type
    category
    Description

    A real situation that should not be allowed: revoked_credential_used, a revoked badge still opening a door.

Findings are deduplicated by key, so replaying a derivation never duplicates one, and they survive recomputation — an alert that was once true stays auditable even after the episode it described has been recalculated.

A type sinio.ai does not know yet

There are two ways an event type can be "not handled", and they are both normal states rather than failures:

What happenedWhat sinio.ai does
Unknown typeThe code is not known to the project at allAccepted and stored as pending_classification, derived once the type is given a meaning
unmapped_event_typeThe type exists, but no episode definition uses itAccepted and stored, not derived, reported as a data_quality finding

Neither drops your traffic. You legitimately send types sinio.ai does not derive anything from yet; treating that as a rejection would lose valid events and make a healthy integration look broken.

Measurements

A device's events often carry values: a battery level, a signal strength, a temperature, a door's state. sinio.ai reads them as measurements, one per entity and measure: a device that sends two batteries sends two measures, each with its own name.

The measure dictionary

Every project has a dictionary of measures: the ones it took from the sinio.ai catalog — each with a function, a nature and a unit, as the catalog defines it — and the ones it created. The catalog is a template: a measure enters the dictionary when a key is attached to it. The function is what sinio.ai looks at: the Fleet, the detection and the assistant never guess a meaning from a code.

CodeFunctionUnit
batteryenergypercent
battery_voltageenergyV
rssisignaldBm
snrsignaldB
signal_qualitysignallevels POOR … EXCELLENT
temperatureenvironmentcelsius
humidityenvironmentpercent
co2environmentppm
pressureenvironmentbar
illuminanceenvironmentlux
powerconsumptionW
energy_consumedconsumptionkWh
passagescountercount
door_statestateCLOSED, OPEN, FORCED
alarm_statestateNORMAL, ALARM

A value is always stored in its measure's unit. A source writing a temperature in fahrenheit, or a voltage in mV, declares that unit and sinio.ai converts it on the way in, so two sources never mix incomparable values in one series.

What a source reads

Every key a source writes in its payloads is inventoried as events arrive, read by something or not. A reading attaches one of them to a measure of the project's dictionary, in the console (To attach, or Settings › Measures): the measure, the entity it is about (device, subject or actor), its path in the payload, the unit it is written in, and the words it translates ("FULL" → 100, "1" → "OPEN"). A reading holds for every event type of the source, or for one type only — a type's own reading wins over the general one. A key nothing reads waits in To attach until it is attached or ignored; a reading whose column gives no readable value is flagged there too.

Slots are stored separately, never averaged. Two battery cells at 10% and 90% are two series, because their mean — 50%, comfortably healthy-looking — hides that half the power supply is gone.

Profiles and norms

An entity's subkind (a probe model, a kind of badge) carries a profile in each project: the measures one of its entities is expected to send, and whether a missing one is worth a finding. The Fleet groups devices by subkind with these columns.

Norms are rules: a limit (threshold) or a rhythm (expected_heartbeat), set in the console on the project, a subkind, a place or a single device. The most precise scope wins — device, then its places, then its subkind, then the project. Without any norm, each device is still compared to its own habit and to the rest of its fleet.

Findings on measurements

KindWhen
measure_limit_crossedA value stays past the limit in force for its duration
measure_off_habitA value leaves the device's own usual range
measure_fleet_outlierA device reads far from the others of its subkind
projected_depletionA battery will be flat within the horizon at its current slope
expected_measure_missingA device keeps talking but stops sending a measure its profile requires
state_too_longA state (a door open, an alarm) lasts longer than tolerated
measure_value_impossibleA source writes a value that cannot be read, or outside the measure's possible range

Where to go next

Was this page helpful?