Docs / Contact / iam/policies

Policies

Policies define fine-grained access rules that control what actions users can perform on which resources. Policies use an IAM-style statement format with namespaced actions.

The editor has two views of the same policy. Policy Builder is the visual form — pick an effect, a resource, and the actions to grant, grouped by namespace — and JSON is the raw statement document for anything the form does not cover, such as conditions written by hand. Switching views never changes the policy; they are two windows onto one document.

The Policy Builder: effect toggle, resource field, action groups by namespace, and condition operators Access Control → PoliciesAdd. This statement grants read and directory actions on 480512:us-1:dir:plant-a — a directory-scoped policy covering every device under /plant-a.

Policy Structure

A policy contains one or more statements. Each statement specifies:

Component Description
Effect Allow or Deny
Action One or more namespaced operations (e.g., entity:ListEntities, flow:CreateConnectors)
Resource Resource path pattern (* for all, or a specific path)
Condition (Optional) Constraints like StartsWith, Contains, or StringEquals

JSON Format

json

{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["entity:ListEntities", "entity:GetEntities"],
      "Resource": "*"
    }
  ]
}

The JSON view of the editor shows this document directly and edits are applied back to the builder, so you can start in the form and finish by hand. Validate Policy checks the document the way the API will before you save it.

The JSON view of the policy editor, showing the same statement as an editable document Access Control → PoliciesAddJSON.

Resource Paths

Resources use the format {accountNumber}:{region}:{resourceType}:{resourceName}. For example:

Use * to match all resources.

Conditions

A condition narrows when a statement applies. Each operator takes a map of field → value, and there are exactly three fields:

Field Compared against
Resource The full resource path of the target ({accountNumber}:{region}:{resourceType}:{resourceName})
Action The namespaced action being attempted (e.g. entity:WriteMessages)
Subject The caller — the user, entity, or API key making the request

and three operators:

Operator Description
StartsWith The field must begin with the given prefix
Contains The field must include the given substring
StringEquals The field must match exactly
Match the full path

Resource conditions compare against the whole resource path, which begins with the account number and region, not the resource type. A prefix like entity:sensor- can never match. To match by name, anchor the prefix on the full path (or use Contains for a substring).

An unrecognized field is ignored, not rejected

Only Resource, Action and Subject are evaluated. A statement whose only condition uses any other field name — including a typo like Subjekt — is treated as having no condition at all, so it applies unconditionally. The policy editor drops unknown fields before saving, but a policy submitted directly to the API is stored as written. Check condition field names when you author policies outside the dashboard.

Example — grant access only to entities named sensor-*:

json

{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["entity:GetEntities", "entity:ListMessages"],
      "Resource": "*",
      "Condition": {
        "StartsWith": {
          "Resource": "12345:us-1:entity:sensor-"
        }
      }
    }
  ]
}

A Contains condition (e.g. "Resource": "entity:sensor-") is a convenient alternative when you want to match a name fragment without hard-coding the account number and region.

Subject conditions and cross-account writes

A Subject condition constrains who the statement applies to. This is the only way to admit a write from another account: a same-account grant, however broad, never authorizes a cross-account writer. The destination account must opt in explicitly, per source:

json

{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["entity:WriteMessages"],
      "Resource": "67890:us-1:*",
      "Condition": {
        "StartsWith": { "Subject": "12345:us-1:" }
      }
    }
  ]
}

Attached to a role on account 67890, this accepts writes from account 12345 and no one else. Without the Subject condition the statement grants nothing across accounts, no matter how permissive it looks.

Action Namespaces

Actions are namespaced by resource type (namespace:Verb). This is the authoritative reference for the actions Contact enforces.

Entity (entity:)

Action Allows
entity:CreateEntities Register entities
entity:ListEntities List entities
entity:GetEntities Read an entity (and its heartbeat)
entity:UpdateEntities Update entity metadata
entity:DeleteEntities Delete entities
entity:WriteMessages Send messages
entity:ListMessages List and search messages across the account
entity:GetMessages Read a single message
entity:CheckMessages Poll for queued messages (/api/entities/check_messages)
entity:GetEntityStatusTable Read entity status tables
entity:UpdateEntityStatusTable Update entity status tables
entity:Stream Publish a video/JPEG stream
entity:ViewStream View an entity's stream
entity:GetFile Download files
entity:SendFiles Upload/send files
entity:SendTerminal Send a terminal command to an entity
entity:FileSystemAccess Issue filesystem commands to an entity
entity:Connect Open an entity connection (WebSocket / MQTT session)
entity:DeployCode Push a remote deployment to entities
entity:Reboot Reboot an entity

Fanout (fanout:)

fanout:CreateFanouts, fanout:ListFanouts, fanout:GetFanouts, fanout:UpdateFanouts, fanout:DeleteFanouts, fanout:PublishToFanout

Directory (directory:)

directory:CreateDirectories, directory:ListDirectories, directory:GetDirectories, directory:UpdateDirectories, directory:DeleteDirectories, directory:MoveEntities

directory:MoveEntities covers filing an entity into a directory or removing it from one. See Directories for how a directory-scoped policy works.

Alert (alert:)

alert:CreateAlerts, alert:ListAlerts, alert:GetAlerts, alert:UpdateAlerts, alert:DeleteAlerts

Dashboard (dashboard:)

dashboard:CreateDashboards, dashboard:ListDashboards, dashboard:GetDashboards, dashboard:UpdateDashboards, dashboard:DeleteDashboards, dashboard:QueryWidgetData

dashboard:QueryWidgetData is what a dashboard widget uses to fetch its series; a role that can view dashboards but lacks it will render empty widgets.

Notification (notification:)

notification:ListNotifications, notification:UpdateNotifications

Flow / Connector (flow:)

flow:ListFlows, flow:GetFlows, flow:ListConnectors, flow:GetConnectors, flow:CreateConnectors, flow:UpdateConnectors, flow:DeleteConnectors

Service (service:)

service:ListServices, service:GetServices, service:CreateServices, service:UpdateServices, service:DeleteServices

IAM (iam:)

Group Actions
Users & invites iam:ListUsers, iam:GetUsers, iam:UpdateUsers, iam:DeleteUsers, iam:ListUserInvites, iam:CreateUserInvites, iam:DeleteUserInvites
Policies iam:ListPolicies, iam:GetPolicies, iam:CreatePolicies, iam:UpdatePolicies, iam:DeletePolicies
Roles iam:ListRoles, iam:GetRoles, iam:CreateRoles, iam:UpdateRoles, iam:DeleteRoles, iam:AssignAdminRole
API keys iam:ListApiKeys, iam:GetApiKeys, iam:CreateApiKeys, iam:UpdateApiKeys, iam:DeleteApiKeys
Account iam:GetAccounts, iam:UpdateAccounts, iam:GetClaims, iam:ListAuditLogs

Insights (insights:)

insights:GetInsights covers all insights endpoints (messages, entities, validation, flows, data usage, summary).

Built-in Policies

Contact includes three built-in policies that cannot be modified or deleted:

Policy Description
Admin Full access to all resources (* action on all resources)
ReadOnly Read and list access to all resource types
DefaultEntity Default entity permissions for device/MQTT connections: send messages (entity:WriteMessages), poll for queued messages (entity:CheckMessages), stream (entity:Stream), and read/update its own status table. It intentionally does not grant entity:ListMessages, so a device cannot list or search other messages on the account.

Policy Evaluation

Policy Inheritance

A policy can name another policy as its parent. The child then starts from everything the parent grants and adds its own statements on top — useful for a house baseline that several roles extend, since editing the baseline updates every policy built on it.

Set a parent in the policy editor with Inherit from an existing policy, or pass parentPolicyId when creating a policy through the API.

The rules, in order:

  1. A child adds to its parent. Every grant the child does not mention is
  2. inherited unchanged.

  3. Deny wins — for the actions it names, and only those. If a child denies
  4. entity:WriteMessages on a resource its parent allowed for several actions, only entity:WriteMessages is denied; the rest of the inherited grant stands.

  5. A child cannot lift an inherited Deny. Re-allowing an action the parent
  6. denies has no effect. To grant it, remove the Deny from the parent.

  7. Conditional grants accumulate. Two statements for the same action and
  8. resource whose conditions differ are two independent grants; both apply. A child adding a Subject condition does not replace the parent's.

  9. Chains resolve top-down, to any depth. A grandparent's grant reaches the
  10. child unless something below denies it.

  11. Cycles are rejected. A policy cannot inherit from itself, directly or
  12. through a chain.

The parent stays live

A child stores only its own statements; the parent's are resolved at evaluation time. Removing a permission from a parent removes it from everything that inherits it, immediately. The editor shows the parent's statements greyed out while you work, but it does not copy them into the child.

Directory-Scoped Policies

A statement whose Resource is a directory path covers every entity filed in that directory and below:

json

{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["entity:GetEntities", "entity:ListMessages", "entity:UpdateEntities"],
      "Resource": "12345:us-1:dir:plant-a"
    }
  ]
}

Grant this and the holder can work with every entity under /plant-a, including anything filed into /plant-a/line-3 later. You never list devices individually, and moving a device between directories moves its access with it.

Matching is exact on path segments: a policy naming /plant does not cover /plant-a. Directory resource paths are shown under each folder in the Directories rail.

Directory scoping covers the full device surface: reading, updating and deleting entities, filing them into folders, and the per-device action endpoints — reboot, the remote command console and filesystem access, SDK pinning, provisioning-key rotation, status-table writes, and publishing to or managing the membership of a fanout filed in the subtree. Code deployments are scoped per target: a deploy to a mixed set delivers to the devices the grant covers and reports denied for the rest.

The one account-scoped exception

notification:UpdateNotifications (marking notifications read) is authorized at the account level. Notifications are account-wide rows with no resource path, so there is nothing narrower to scope them to.

Moving an entity checks both ends

directory:MoveEntities is checked against the entity being moved and against the destination folder (with its ancestor chain, so a grant on /plant-a covers filing into /plant-a/line-3). An operator scoped to one subtree cannot file devices into folders outside their grant. Removing an entity from a folder checks only the entity — unfiling has no destination.