Thing access model

How a thing becomes visible to an account, and how an account shares it with another account. This is the model behind the /access/v1/… capability group; the endpoint contracts are in the API reference and the commands in the CLI reference.

The four objects

Thing

A device, product, or piece of equipment registered on the platform. Things move through a lifecycle — see Thing lifecycle.

Registrar

A manufacturing context owned by an account. Things are enrolled into a registrar. An account can have several (different factory lines, product categories).

Repository

An account-owned collection of things — your view of the fleet. An account can have several, uniquely named within the account, and one is the default. A thing is in your repository because a reference row says so; that reference is what makes it visible to you.

Scope

A named selection of things inside one repository. Scope is the only grouping concept: there are no device groups and no tag buckets. A same-repository union is just another scope; a cross-repository union is a set of grants, not one object.

The rules you have to obey

These are the invariants the API enforces. Design against them, not around them.

  1. Visibility comes from the repository reference, never from the registrar. Registering a thing does not, on its own, authorize you to read it — registration inserts the reference that does. Nothing in the API lets “I registered it” stand in for “it is in my repository”, so do not build a client that assumes the two are the same relation. They diverge routinely: platform-owned shared devices belong to no tenant registrar, and a manufacturing-line registrar’s things are read by end-user accounts that never owned the line.

  2. A visibility miss is 404, not 403. This is deliberate anti-enumeration: a 403 would confirm that a thing exists in someone else’s account. Do not treat 404 as “bad identifier” — on any thing-, scope- or grant-scoped route it equally means “not referenced into your repository”. Retrying with a corrected id is the wrong reflex; check your reference first.

  3. Removing the reference removes visibility, with no second path. There is no shadow route through the registrar relation, no cached grant, and no owner override on the tenant surface. DELETE /access/v1/repositories/{repository_id}/things/{thing_id} is sufficient and immediate.

  4. Everything account-scoped hangs off the repository. Scopes are repository-bound and grants are scope-bound, so deleting a scope cascades away the grants over it. Your default repository is materialized on first touch — a brand-new account can call GET /access/v1/repositories and get a usable answer without a setup step.

  5. A grant delegates named capabilities, never a role. There is no reader / operator / admin tier to ask for. See the table below.

  6. Authorization is decided centrally and fails closed. A 403 means the platform decided your principal may not perform that action on that resource. Adding a role or a claim to your token cannot change the outcome, and a decision that cannot be reached is a denial, not an allow. See Identity, grants, and application access.

Sharing a thing with another account

A grant binds a grantee account to one scope (which things) with a set of capabilities (what they may do):

fgai access grants-create --body '{
  "grantee_account_id": "acc-distributor-01",
  "scope_id": "scp-line-3",
  "caps": ["telemetry:read", "attribute:read"] }'

Capability

What it permits

telemetry:read

read device telemetry

attribute:read

read device attributes

attribute:write

write device attributes

command:invoke

send a device command

registry:read

read registry entries for the scoped things

registry:write

write registry entries for the scoped things

lifecycle:transfer

transfer ownership of a scoped thing

lifecycle:decommission

decommission a scoped thing

telemetry:usage-read

read derived usage counters for the scoped things — never the telemetry itself

The set is closed: an unrecognized capability is rejected with 400, never silently dropped. Capabilities are granted individually — request exactly the ones the integration uses.

There is one grant per (grantee, scope, app namespace). Re-granting the same triple is a 409 — update its capability set instead of creating a second row. Revoke by deleting the grant, or by deleting the scope it is bound to.

A grant takes effect shortly after it is written, not in the same instant: the authorization it creates is projected asynchronously. If you are testing a fresh grant — or a revoke — re-try a moment later before concluding the decision is wrong.

attribute:write is additionally single-writer over a scope: if another grant already delegates write over the same scope, a second one is refused with 409, including on the update path. A device’s cloud-written configuration is one state with one author at a time — the owner, or the owner’s single delegate. Revoke the existing delegation before naming a different one. Reading, commanding, and telemetry stay freely grantable to as many accounts as you like.

An app_namespace on a grant narrows telemetry to one app’s records on the scope’s devices, for a software publisher that supports its own app rather than the whole machine. Such a grant carries exactly ["telemetry:read"]. See App-scoped telemetry and usage metering.

Requesting access you have not been given

Where the owner has not pre-granted, the grantee asks and the owner decides:

fgai provisioning access-requests-list
fgai provisioning access-requests-decide --id ar-123 --body '{"decision":"approve"}'

An approval issues a short-lived approval token; it expires, so approvals are acted on promptly rather than banked. The request lifecycle is part of the provisioning capability group.

Where things go next