Models
A model is the shape of a thing in Yggio: the fields the REST API accepts when you create or update it, and the fields it returns when you read it. The API validates every request against these schemas, so a body that does not match is rejected before anything is stored.
Entities of these models can be fetched, created, modified and deleted through the REST API.
Each created model entity will get a _id for referencing purposes. An Id is a 24 characters long hex string (e.g. 507f1f77bcf86cd799439011).
The models
| Model | What it is |
|---|---|
| User | An account |
| Usergroup | A set of users |
| Iotnode | A device, physical or virtual. The central model |
| Connector | A link to a network server or an external system that devices arrive through |
| Calculation | A derived value computed from device data |
| Access right | Who may reach a resource, and at what level |
| Channel | An outbound subscription that pushes updates to your service |
| BasicCredentialsSet | A username and password pair used by integrations |
| ReservedMqttTopic | An MQTT topic reserved to a credentials set |
| Location | A place on the map that groups devices. Deprecated |
Other models exist and are reachable over the API - dashboards, geofences, device groups, reports, rules, images, organizations and more. They follow the same conventions as the ones below. Every one of them is also a resource that carries access rights; Access rights lists the full set of resource types.
The full schema
This page describes the fields you will use most. The complete schema for every endpoint is in the Swagger UI. It is maintained alongside the API rather than generated from it, so it can lag behind a recent change - if a request behaves differently from what Swagger shows, the running endpoint is what counts.
Swagger is also the quickest way to see what a specific request rejects: expand the endpoint, read the model under Request body, and use Try it out against your own account.
User
A user is an account. It is used for authentication and for access control to other resources. Accounts are held in Keycloak, the login service Yggio uses, which is why the field names below follow Keycloak's rather than Yggio's usual style.
{
username: String, // required
email: String,
firstName: String,
lastName: String,
phoneNumber: String, // E.164 format, e.g. +46701234567
enabled: Boolean,
requiredActions: [String],
attributes: {
globalVisibility: String, // 'true' or 'false', not a boolean
language: String,
organization: [String],
roles: [String]
}
}
username is the only required field on create.
phoneNumber must be in E.164 format.
enabled controls whether the account may log in. Disabling an account does not remove it.
requiredActions lists actions Keycloak will force at next login, such as setting a password.
attributes holds the Yggio-specific extras. Note that globalVisibility lives here rather than at
the top level, and is stored as the string 'true' or 'false' rather than as a boolean. It
controls whether the username can be searched for by other Yggio users.
There is no password field, and no public endpoint for setting one. Passwords live in Keycloak and
are set either by Keycloak itself at login, through requiredActions, or by Yggio when an
organization creates or resets a member.
Usergroup
A user group is a set of users. It is used for two things: granting access rights to several people at once, and filtering which apps its members see.
The group itself is a small object:
{
_id: Id,
name: String, // required, 2 to 60 characters
owner: Id // required
}
name must be between 2 and 60 characters.
owner is a reference to the user that owns the group. When creating a group, the owner is the user
that created it.
Members and the app filter are not fields on this object. They are managed through their own endpoints, so a create call that includes them will not do what it looks like:
| What | How |
|---|---|
| Add a member | POST /usergroups/{groupId}/members |
| Remove a member | DELETE /usergroups/{groupId}/members/{memberId} |
| Read the members | GET the group with includeMembers=true |
| Set the app filter | PUT /usergroups/{groupId}/app-filter |
| Allow one app | POST /usergroups/{groupId}/app-filter |
| Remove one app | DELETE /usergroups/{groupId}/app-filter/{appId} |
Iotnode
An iotnode is usually a representation of a physical device. However, it can also be a virtual device.
The iotnode model is deliberately open. Only a few fields are fixed; everything else a device reports is stored alongside them as top-level fields. That is what lets one model carry a temperature sensor, a people counter and a water meter without a schema per device type.
{
_id: Id, // assigned by Yggio
name: String, // required on create, non-empty
description: String,
category: String,
connector: Id, // the connector the device arrives through
reportedAt: Date,
updatedAt: Date,
rabbitRouting: Object,
// ...plus every field the device's translator produced
}
name is required and must be a non-empty string. It may not contain the sequence ~||~, which
Yggio uses internally as a separator.
description is for display purposes.
category is used for grouping iotnodes to enable easy filtering.
connector references the connector the device arrives through. It can be supplied as
an id or as an object with an _id.
reportedAt is a timestamp that states when the iotnode last got a report from the physical device.
updatedAt is a timestamp that states when any attribute was last updated (e.g. name, value etc.).
rabbitRouting is only used internally, and should not be modified by external developers.
The data fields
The measurement fields on an iotnode are not part of this model. They are written by the device's
translator, which decodes the raw payload into Yggio's
canonical data model - temperature, relativeHumidity, batteryVoltage, co2 and so on, each
with a declared type, unit and quantity.
This matters when writing against the API: do not expect a fixed set of value fields, and do not invent your own names for values a translator already produces. The Translator API covers the field-level reference.
Because the model accepts unknown fields, a misspelled field name is stored rather than rejected. A device that appears to be missing a value is worth checking for this first.
Connector
A connector is the link between Yggio and the system devices arrive through: a LoRaWAN network server such as ChirpStack or Netmore, a vendor cloud, or a generic HTTP or MQTT feed. Yggio supports 26 integrations.
{
_id: Id,
name: String, // required
integration: String, // required - which integration this is
flowIdentifier: String, // optional, selects a custom flow
retentionPolicy: String,
protocols: Object, // protocol-specific settings
uplink: {protocol: String},
downlink: {protocol: String},
// ...plus fields unique to the integration
}
integration selects the type and decides which further fields apply, so the rest of the body
differs per integration. Read the exact shape for the one you are using under Connectors in
Swagger, and see Connectors for what each does.
Calculation
A calculation derives a value from device data and writes it back as a field, so it can be charted, reported on and alarmed like any other value.
There are two shapes, chosen by type:
type | What it does |
|---|---|
lastValue | Aggregates the most recent value of one or more source devices |
window | Aggregates a window of readings from a single source over time |
Both carry a source (what to read), a destination (where to write the result), the aggregation
method, and an optional automatic-update schedule. The full field list for each shape is in Swagger
under Calculations, as CalculationLastValue and CalculationWindowed.
Access right
An access right grants one subject a level of access to one resource. It is not a field on the
resource; it is its own record, created and removed through /access-rights.
{
resourceType: String, // 'device', 'dashboard', 'image', ...
resourceId: Id, // the resource's _id, 24-character hex
subjectType: String, // 'singleton', 'group' or 'orgUnit'
subjectId: Id, // a Keycloak UUID for 'singleton', a 24-character hex _id for 'group'
scope: [String] // 'admin', 'write', 'read', 'peek'
}
subjectType is singleton for a single user, group for a user group, or orgUnit for an
organization unit. Singleton is the access engine's term: every user maps onto a group of one, which
is what lets users, groups and units be granted access through the same mechanism. The control panel
sends singleton, and user is accepted as an alias for it.
The two id kinds are not interchangeable. A user is identified by its Keycloak id, which is a UUID;
a group by its own _id, a 24-character hex string like every other Yggio resource id.
One endpoint departs from this. GET /access-rights/subject/{id} takes only user or group in
its subjectType query parameter, and defaults to user.
Every resource type in Yggio can carry access rights, including ones with no user interface for it. See Access rights for the resource types and what each level allows.
Channel
A channel pushes updates to an external service when device data changes. The message carries the device as well as which attributes were updated.
{
name: String,
// exactly one target
iotnode: Id,
deviceGroup: Id,
// exactly one protocol
http: {url: String, headers: Object},
mqtt: {type: String, recipient: Id},
azureIotHub: {connectionString: String},
desigoCC: {...},
deltaControls: {...},
vyer: {...}
}
Two rules are enforced on create, and both are a common cause of a rejected request:
- Exactly one target. Either
iotnodeordeviceGroup, never both and never neither. A channel on a device group follows the group, so devices added to it later are covered without a new channel. - Exactly one protocol. Setting none, or more than one, is rejected with a message naming the ones it found. The six above are the ones the protocol dropdown offers.
name is optional.
http.url must be a valid URL. Messages are POSTed to it.
http.headers is an optional object of additional HTTP headers sent with every request. For Basic
Auth, include an Authorization header set to Basic <base64>, where <base64> is the Base64
encoding of username:password. Any Authorization header is write-only: it is accepted on create
and update but never returned by the API.
mqtt.type is either keycloakUser or basicCredentialsSet, and mqtt.recipient is the id of
whichever of those two it is.
BasicCredentialsSet
A BasicCredentialsSet is a username and password pair that a device or an integration authenticates with. It is used when publishing MQTT messages to Yggio, and as the recipient of an MQTT channel.
{
username: String, // required
password: String // required
}
username may not be a UUID; that shape is reserved and is rejected.
password is salted and hashed on the way in, and is never returned by the API.
ReservedMqttTopic
A ReservedMqttTopic reserves an MQTT topic and pairs it with a BasicCredentialsSet, so only that credential can publish to it.
{
topic: String, // required
basicCredentialsSetId: Id // required
}
basicCredentialsSetId must reference a credentials set that exists; the create call checks it.
topic is validated, and three rules decide whether it is accepted:
- It may not start with
/. - It must start with one of the allowed root topics:
yggio/generic/v2,yggio/axis/v1,yggio/beijerix/v1,yggio/hubitat/v1oryggio/push/v1. - It must add a sub-topic below that root. The root on its own is not a reservation.
So yggio/generic/v2/my-building is accepted, while yggio/generic/v2 and
my-building/sensors are not. The error message names the allowed roots.
Location (deprecated)
The location model backs the Location Manager, which is deprecated. It is documented here for those still using it. Do not build new integrations on it. It groups iotnodes together and binds them to a place on the world map.
Location data structure:
{
name: String, // required
description: String,
user: Id, // required
lat: Number, // required
lng: Number, // required
icon: String,
defaultLayer: LocationLayer, // required
layers: [LocationLayer],
version: Number
}
name is required, and is validated against a character set: letters, digits, the Swedish
characters åäöÅÄÖ, spaces, underscores and hyphens. Other characters are rejected with
"is not a valid location name".
description is for display purposes.
user is a reference to the user who created the location, and is required.
lat and lng are required coordinates for where the location sits in the world.
defaultLayer is required and is the layer shown first when entering a location. layers holds the
rest. See LocationLayer below.
LocationLayer
LocationLayer is used to group items (iotnodes) in a location. For example: if a location is a building, a locationLayer can be seen as a floor in that building.
{
name: String,
items: [LocationItem],
image: String, // path to the background image
left: Number, // position of the image, 0 to 100
top: Number,
width: Number, // size of the image, 50 to 200
height: Number
}
name accepts the same character set as the location name.
image is the background drawing the items are placed on, such as a floor plan, and left, top,
width and height position and size it.
LocationItem
LocationItem is a wrapper for an individual iotnode in a location.
{
deviceId: Id, // required
type: String,
size: String,
top: Number,
left: Number,
valueDisplay: String,
locationItemControl: {icon: String}
}
deviceId is a reference to an iotnode, and is the only required field.
top and left place the item on the layer, and size, type and valueDisplay control how it is
drawn and which value it shows.