Magistrala
Dev Guide

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 Domain Alias).
  • Tags, Status, Attributes (free-form JSON, replaces the old flat Metadata field), 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"`
}
  • Kind replaces the old separate Client/Channel structs — "device" and "channel" are the two kinds the platform's own UI and CLI use today.
  • ExternalID is the successor to the old Credentials.Identity concept — 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 = true and an attributes.gateways list identifying which gateway(s) may publish/observe on its behalf. See the Gateway Management user guide for the reachability model this enables.
  • DeviceTypeID/DeviceTypeVersionID bind a device entity to a Device Type — a versioned schema Atom validates the entity's Attributes against 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 typed Credentials/Metadata fields — 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

OperatorSymbolURL-encodedBehaviour
OR,,Returns entities that have at least one of the specified tags
AND+%2BReturns entities that have all of the specified tags

Examples

OR filter — return entities tagged temperature or humidity:

GET /workspaces/{workspaceID}/channels?tags=temperature,humidity

AND filter — return entities tagged both outdoor and sensor:

GET /workspaces/{workspaceID}/channels?tags=outdoor%2Bsensor

Note: Because + is interpreted as a space in URL query strings, always percent-encode it as %2B when 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.

On this page