Overview
Understand core Magistrala entities — Workspaces, Devices, Channels, Gateways, Device Types and Groups — and how they interact within the IoT platform.
Identity, tenancy, entities, and authorization are owned by Atom, a separate Rust service
Magistrala's Go code talks to over gRPC/GraphQL. The standalone Go structs (User, Domain,
Role, Group, Client, Channel) owned by the old per-entity services no longer apply.
Magistrala organizes everything under a Workspace (an Atom Tenant). Within a workspace, most of what used to be separate entity types — Devices, Channels, Gateways — are now one generic, kind-discriminated Entity type. Groups remain a separate hierarchical structure for organizing entities. This page explains those shapes; see Authorization for how access to them is controlled.
Workspace (Atom Tenant)
type Tenant struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Route string `json:"route,omitempty"`
Tags []string `json:"tags,omitempty"`
Status string `json:"status,omitempty"`
Attributes Attributes `json:"attributes,omitempty"` // map[string]any
CreatedBy string `json:"created_by,omitempty"`
UpdatedBy string `json:"updated_by,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}ID— unique tenant identifier.Name— the workspace's display name.Route— a URL-safe slug used to address the tenant (successor to the old DomainAlias).Tags,Status,Attributes(free-form JSON, replaces the old flatMetadatafield),CreatedBy/UpdatedBy/CreatedAt/UpdatedAt— as before.
The API/CLI for interacting with Workspaces is described in the Workspaces CLI. There is no workspaces REST section in API — workspace management goes through Atom's GraphQL API, which the CLI wraps; see the callout on that page.
Entity — Devices, Channels, and Gateways
Devices, Channels, and Gateways are not separate Go types. They are all atom.Entity, discriminated by Kind:
type Entity struct {
ID string `json:"id,omitempty"`
Kind string `json:"kind"` // e.g. "device", "channel"
Name string `json:"name"`
ExternalID string `json:"external_id,omitempty"` // caller-assigned ID: serial, MAC, SKU
TenantID string `json:"tenant_id,omitempty"`
// Bind to a Device Type — see below. Both are write-once-per-call:
// leaving them empty on an update keeps the existing binding.
DeviceTypeID string `json:"device_type_id,omitempty"`
DeviceTypeVersionID string `json:"device_type_version_id,omitempty"`
Status string `json:"status,omitempty"`
Attributes Attributes `json:"attributes,omitempty"` // map[string]any
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}Kindreplaces the old separateClient/Channelstructs —"device"and"channel"are the two kinds the platform's own UI and CLI use today.ExternalIDis the successor to the oldCredentials.Identityconcept — an opaque, caller-assigned identifier (device serial/MAC/SKU), unique per tenant among live entities.- A Gateway is not a third kind. It's a device entity with
attributes.is_gateway = trueand anattributes.gatewayslist identifying which gateway(s) may publish/observe on its behalf. See the Gateway Management user guide for the reachability model this enables. DeviceTypeID/DeviceTypeVersionIDbind a device entity to a Device Type — a versioned schema Atom validates the entity'sAttributesagainst on every write. Note the Go comment on this field is explicit that Atom's own internal name for this concept is "Profile," never exposed publicly because it collides with Bootstrap's unrelated Profile concept — always say Device Type in docs and UI copy.Attributes(map[string]any) replaces the old typedCredentials/Metadatafields — everything caller-defined about an entity, including secrets/credentials where applicable, lives here now.
Secrets/credentials for publishing (what used to be Client.Credentials.Secret) are still issued per device and sent as Authorization: Client <secret> on the wire — the HTTP header keyword itself was not renamed even though the entity kind was.
The API/CLI for interacting with Devices is in the Devices CLI; Channels in the Channels CLI; Gateways in the Gateways CLI; Device Types in the Device Types CLI.
Group
Groups are still a distinct type — a hierarchy for organizing entities within a workspace:
type Group struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
TenantID string `json:"tenant_id,omitempty"`
Description string `json:"description,omitempty"`
ParentID string `json:"parent_id,omitempty"`
Status string `json:"status,omitempty"`
Attributes Attributes `json:"attributes,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}The role/access fields the old Group struct carried (RoleID, RoleName, Actions, AccessType, AccessProvider*) are gone from the type itself — access is now expressed separately via Atom's authorization primitives (PermissionBlock, ActionAssignmentRule, DirectPolicy/GroupGrant), covered in Authorization.
The API/CLI for interacting with Groups is in the Groups CLI.
Users and roles
There is no dedicated User Go struct owned by Magistrala anymore, and no users CLI command — user accounts, authentication, and role/permission assignment are owned entirely by Atom. See the CLI Introduction for what's actually available from Magistrala's side (login, authz check), and Authorization for the permission model.
Tags Filtering
Tags are supported on Workspaces, Groups, and Entities (Devices/Channels). When listing any of these, the tags query parameter can be used to filter results by one or more tag values.
Operators
| Operator | Symbol | URL-encoded | Behaviour |
|---|---|---|---|
| OR | , | , | Returns entities that have at least one of the specified tags |
| AND | + | %2B | Returns entities that have all of the specified tags |
Examples
OR filter — return entities tagged temperature or humidity:
GET /workspaces/{workspaceID}/channels?tags=temperature,humidityAND filter — return entities tagged both outdoor and sensor:
GET /workspaces/{workspaceID}/channels?tags=outdoor%2BsensorNote: Because
+is interpreted as a space in URL query strings, always percent-encode it as%2Bwhen constructing AND filter requests manually.
Tag Value Constraints
Because , and + are reserved as filter operators, tag values themselves must not contain these characters. A tag such as outdoor+sensor or type,water would conflict with the query syntax and produce incorrect filter results.
Allowed examples: temperature, meter-type:water, zone-A
Audit logs
The old standalone journal service is gone. Entity/channel/group activity (creation, updates, disabling, connectivity, role changes) is now tracked by Atom's own audit log, exposed via a GraphQL query (magistrala-ui's packages/ui/src/lib/journal.ts, which the UI's per-entity Journals tab calls):
query ObjectAuditLogs($targetKind: String!, $targetId: ID!, $event: String, $limit: Int, $offset: Int) {
auditLogs(targetKind: $targetKind, targetId: $targetId, event: $event, limit: $limit, offset: $offset) {
total
items {
id
event
outcome
details
createdAt
}
}
}targetKind follows Atom's own event-recording convention (atom: src/graphql/{resources,entities,groups}.rs): "resource" for Channels, "group" for Groups, "entity" for everything else (Devices, Users, Gateways, Device Types).
Atom-backed deployments only
This query is only available when the deployment targets Atom (MG_ATOM_URL set). Legacy
deployments without Atom fall back to the old journal SDK — not documented here since that
service no longer exists in this repository.
Attributes / Metadata
Attributes (map[string]any in Go, called Metadata in the old per-entity structs) is a free-form JSON object available on Tenants, Entities, and Groups, used to attach arbitrary structured data.
UI-Compatible Format
When metadata is managed through the Magistrala UI, each user-defined key is stored with type information:
{
"myKey": {
"value": "my value",
"type": "string",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
}Supported types are: string, integer, double, boolean, json, location, perimeter.
Entities provisioned via the API or CLI with raw (unwrapped) values — e.g. {"myKey": "my value"} — are accepted and displayed in the UI, but will not have type tracking or an updatedAt timestamp. To ensure full UI compatibility, use the wrapped format above.
For complete details on metadata value types and the location/perimeter formats used by dashboard map widgets, see the Metadata Management user guide.