Resources¶
Every client.<resource> is an instance of one of these classes.
Reads¶
TagsAPI ¶
Bases: ResourceAPI
List, get, and (later) mutate trackables.
The wire name for trackables is trackable_objects — exposed as
tags here because "tag" is what operators actually call them.
Note on method order: list is defined last in this class so that
mypy resolves list[Tag] annotations in other methods to the
builtin list and not to TagsAPI.list. (Python class-scope
shadowing limitation, even with PEP-563 deferred annotations.)
get ¶
Fetch a single trackable by uid.
The server returns {trackableObjects: [t]} even for single
gets — a one-element list wrapper. The SDK unwraps it. An empty
wrapper (meaning the uid isn't valid) raises :class:NotFound.
list_active_for_period ¶
Trackables active in [start, end] inside one sublocation.
Timestamps go on the wire as epoch milliseconds.
list_existed_for_period ¶
Trackables that existed at any point in [start, end].
list_templates ¶
Return all tag-creation templates available on the server.
Server-side templates are pre-baked configurations operators reuse when creating new trackables.
last_position ¶
Return the last cached position for a trackable, if known.
The response envelope is {trackablePositionFromState} — a
single position object, not a list (RESEARCH §3). Returns
None if the server has no cached position.
create_raw ¶
Create a tag (single REST call, no zone / group plumbing).
Advanced. Most users want :meth:create (added in M4) which
also handles attached-zone and group binding automatically.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Required. The tag's name. |
required |
**fields
|
Any
|
Any other field the server's |
{}
|
Notes
Tags do NOT carry a mac_address. The MAC belongs to a
:class:Node (the hardware), bound to a tag via an
:class:TagAssociation (trackable_objects_associations). Passing
mac_address= here is a silent no-op — the server drops it.
If your workflow is "I have a MAC and want a tag bound to it,"
call :meth:NodesAPI.create + :meth:TagAssociationsAPI.create
in sequence. A compound tags.create_with_node that wraps
the saga is planned for a future milestone.
Returns:
| Type | Description |
|---|---|
Tag
|
The created tag as returned by the server. |
update_raw ¶
Update a trackable (single REST call).
Advanced. Most users want :meth:update (added in M4) which
diff-applies zone/group changes alongside the field updates.
delete_raw ¶
Delete a trackable (single REST call, no zone-cascade).
Advanced. Most users want :meth:delete (added in M4) which
also deletes the trackable's attached zone if any.
replace_groups ¶
Replace the set of groups a trackable belongs to.
Two server paths exist:
PUT /trackable_objects/{uid}/groupswith a bare-array body (per RESEARCH §5 quirk #15 and the JS reference client).PUT /trackable_objects/{uid}with{"groups": [...]}in the body (the standard tag-update endpoint).
The live test server treats the dedicated /groups endpoint
as a silent no-op (returns 204, doesn't persist) — only the
tag-update path actually writes membership. The SDK uses the
tag-update path because it's the one that works.
no_notify is accepted for API stability but ignored on the
tag-update path; the server doesn't broadcast a group-change
event from there.
create ¶
Create a tag, optionally attaching a zone and group memberships.
One call collapses up to four REST round-trips (create tag,
create bound zone, replace groups, update tag with
self_zone_uid). On any sub-step failure the SDK attempts to
roll back the created tag and zone; if a rollback fails, the
raised :class:PartialFailureError carries the orphaned uid in
its partial_result so the caller can clean up.
Step names (for PartialFailureError.failed_step):
create_tag, create_zone, replace_groups,
update_tag_with_zone_uid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Required tag name. |
required |
attached_zone
|
dict[str, Any] | None
|
Optional zone spec dict — |
None
|
groups
|
list[str] | None
|
Optional list of group uids the tag should belong to. |
None
|
**fields
|
Any
|
Other tag fields the |
{}
|
Notes
Tags do NOT carry a mac_address. The MAC belongs to a
:class:Node, bound via an :class:TagAssociation. See the
Notes on :meth:create_raw for how to bind hardware to a tag.
Examples:
update ¶
Diff-driven update of a tag's fields, attached zone, and groups.
attached_zone / groups accept three forms:
- default (
...— sentinel): leave unchanged. None: remove (delete bound zone / clear groups).- dict / list: set or replace.
Step names: load_current_tag, delete_attached_zone,
create_attached_zone, update_attached_zone,
replace_groups, update_tag.
Unlike :meth:create, partial updates cannot be unwound
cleanly. On failure the SDK raises :class:PartialFailureError
listing the completed_steps so the caller can reconcile.
delete ¶
Delete one or many tags, cascading to attached zones.
- Single uid (
str) or :class:Tag: returnsNone, raises :class:PartialFailureErroron failure. - List: returns :class:
BulkResultwith per-item errors; an error on one row does NOT abort the others.
Passing :class:Tag instances skips the per-tag GET that would
otherwise be needed to discover self_zone_uid.
Step names (single-target variant): load_tag,
delete_attached_zone, delete_tag.
bulk_update ¶
Apply the same updates to many tags, continue-on-error.
Each tag is updated via :meth:update (so attached_zone and
groups inside changes get the same diff semantics as the
single-tag form). Failures land in BulkResult.failures.
list_active_in_period ¶
Tags active in [start, end] inside one sublocation.
Inner-joins trackable_objects/active_for_period with the
active-association list — the union the JS reference client
manually composes (RESEARCH §4 "Load trackables with active
associations for a period").
list ¶
Return active trackables in the current project scope.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
include_removed
|
bool
|
Include soft-deleted trackables. Defaults to |
False
|
company_scope
|
bool
|
If |
False
|
Returns:
| Type | Description |
|---|---|
list[Tag]
|
Possibly empty. |
Examples:
SitesAPI ¶
Bases: ResourceAPI
List, create, update, and delete physical sites in the current scope.
The wire-format name is locations; SDK exposes them as sites
because that's the operator-facing term.
Cascade contract: sites.delete(uid) deletes the site and
every area, floorplan, anchor, and zone under it (server-side
cascade). The SDK does not pre-emptively delete children.
list is defined last (class-scope shadow workaround).
create ¶
Create a site.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Display name. |
required |
shift
|
dict[str, Any] | None
|
Optional shift configuration (the JS reference client's only documented update field — passing it on create is also legal). |
None
|
**fields
|
Any
|
Additional fields the deployment supports ( |
{}
|
Examples:
update ¶
Update a site's name and / or shift configuration.
Only name and shift are accepted — passing any other
field raises :class:ValueError before any HTTP call is made.
This mirrors the JS reference client's conservative payload
(RESEARCH Discrepancy #13). If you have a use case that needs
other fields updated, file an issue — the SDK can be opened up
once the server-side contract is confirmed.
delete ¶
Delete a site (cascades to areas / floorplans / anchors / zones server-side).
A subsequent GET returns 404 → :class:NotFound. The cascade is
the server's contract, not the SDK's — the SDK does NOT iterate
children before issuing the DELETE.
get ¶
Fetch a single site by uid.
The server returns {locations: [s]} — a one-element list —
even for a single-uid GET; the SDK unwraps it. A missing uid
(empty wrapper or HTTP 404) raises :class:NotFound.
list ¶
AreasAPI ¶
Bases: ResourceAPI
List, create, update, and delete areas (sublocations) in the current scope.
The wire-format name is sublocations; SDK exposes them as
areas to match operator terminology.
Cascade contract: areas.delete(uid) removes the area and every
floorplan, anchor, and zone bound to it (server-side cascade). The
SDK does not pre-emptively delete children.
list is defined last (class-scope shadow workaround).
create ¶
Create an area inside the site site_uid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Display name. |
required |
site_uid
|
str
|
Parent site uid. Required. Sent to the server as its
|
required |
**fields
|
Any
|
Additional optional placement metadata: |
{}
|
Examples:
update ¶
Update an area's fields.
The body is the kwargs verbatim — the SDK does not enforce an
allowlist on area updates, mirroring the desktop site-plan
client's updateItem flow. The one exception is the canonical
site_uid: it is translated to the server's location_uid
(an explicit location_uid kwarg, if given, takes precedence).
get ¶
Fetch a single area by uid.
The server returns {sublocations: [a]} — a one-element list —
even for a single-uid GET; the SDK unwraps it. A missing uid
(empty wrapper or HTTP 404) raises :class:NotFound.
FloorplansAPI ¶
Bases: ResourceAPI
List, create, update, delete floorplans; upload + download images.
list is defined last (class-scope shadow workaround).
get ¶
Fetch one floorplan by uid.
The server returns {floorplans: [<item>]} — a one-element
list — even for a single-uid GET; the SDK unwraps it. A missing
uid (empty wrapper or HTTP 404) raises :class:NotFound.
create ¶
create(*, sublocation_uid, image_bytes=None, original_filename=None, image_path=None, display_name=None, image_scale=1.0, image_rotation=0.0, image_offset_x=0.0, image_offset_y=0.0, **fields)
Create a floorplan attached to sublocation_uid.
Image input is required and either-or:
- Pass
image_bytes(bytes) +original_filename(str), OR - Pass
image_path(str | Path) — the SDK reads the file and derivesoriginal_filenamefromPath(image_path).name.
Passing both raises ValueError. Passing neither raises
ValueError — the server schema requires an image.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sublocation_uid
|
str
|
Parent area uid. Required. |
required |
image_bytes
|
bytes | None
|
Raw image bytes (PNG / SVG / PDF). Must be valid for its
format — the server runs |
None
|
original_filename
|
str | None
|
File name as the server should store it. Required when
|
None
|
image_path
|
str | Path | None
|
Convenience: a path on disk. Mutually exclusive with
|
None
|
display_name
|
str | None
|
Optional user-facing label (the server's |
None
|
image_scale
|
float
|
Placement transform. Identity defaults. |
1.0
|
image_rotation
|
float
|
Placement transform. Identity defaults. |
1.0
|
image_offset_x
|
float
|
Placement transform. Identity defaults. |
1.0
|
image_offset_y
|
float
|
Placement transform. Identity defaults. |
1.0
|
**fields
|
Any
|
Other server-accepted fields (deployment-specific). |
{}
|
Examples:
From in-memory bytes:
>>> with open("warehouse.png", "rb") as fh:
... plan = client.floorplans.create(
... sublocation_uid=area.uid,
... image_bytes=fh.read(),
... original_filename="warehouse.png",
... )
From a path:
update ¶
Update a floorplan's fields. Image replacement is optional.
Same either-or rule for image inputs as :meth:create. Pass
neither to keep the stored image untouched; pass one to replace
it.
download_image ¶
Download a floorplan's image bytes.
Accepts a :class:Floorplan (uses its .url) or a uid (the
SDK first calls :meth:get to resolve). The request issues a
single GET against the server-returned relative URL with a
180-second read timeout — production floorplans can be tens of
MB of PDF, well beyond the default 30-second read budget.
With stream_to=None (default), accumulates the full body
and returns bytes.
With stream_to=<writable bytes file-like>, writes chunks
as they arrive and returns None. Use this for very large
downloads to keep memory bounded.
Examples:
ZonesAPI ¶
Bases: ResourceAPI
List, get, and (later) mutate zones (static + buffers).
list is defined last so mypy resolves list[Zone] annotations
in other methods to the builtin (class-scope shadow workaround).
get ¶
Fetch a single zone by uid.
The server has no dedicated GET /zones/{uid} per the JS
client; the SDK composes via list() and filters. This is a
documented stop-gap — if the server later exposes a single-zone
endpoint, the implementation switches without changing the
surface.
history ¶
Return zones (including soft-deleted) active during [start, end].
Used for audit / replay workflows. Timestamps go on the wire as epoch milliseconds.
list_custom_prototypes ¶
List custom-zone prototypes — server-side reusable zone shapes.
These don't yet have a dedicated model; we surface them as raw dicts. M2's pydantic shapes will replace this when usage stabilizes.
create_raw ¶
Create a zone or buffer (single REST call).
Advanced. :meth:create (added in M4) wraps this and also
handles dynamic zone-to-trackable binding. Use create_raw
only when you need exact control over the call sequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Required. Zone name. |
required |
**fields
|
Any
|
Pass |
{}
|
update_raw ¶
Update a zone (single REST call, no trackable re-binding).
Advanced. :meth:update (added in M4) handles dynamic-zone
re-binding automatically.
delete_raw ¶
Delete a zone (single REST call, no trackable-detach).
Advanced. :meth:delete (added in M4) clears any dynamic
binding before deletion.
create ¶
Create a zone or buffer, binding it to a trackable if requested.
For static zones (dynamic=False) this is a single REST call.
For dynamic zones bound to a trackable, the SDK also writes
self_zone_uid onto the trackable so the binding is consistent
from both sides. The trackable update is rolled back (best
effort) if the zone create succeeds but the binding fails.
Step names: create_zone, set_trackable_binding.
Examples:
update ¶
Update a zone, re-binding the trackable if requested.
Pass trackable_uid=... (default) to leave the current
binding alone. Pass trackable_uid=None to clear it;
trackable_uid="t-x" to rebind to a different trackable.
Step names: load_zone, clear_old_binding,
set_new_binding, update_zone.
If set_new_binding fails after clear_old_binding
succeeded, the SDK best-effort restores the old binding before
raising. If that restore also fails, the exception's
partial_result names the dangling trackable.
delete ¶
Delete a zone, clearing any dynamic-trackable binding first.
Step names: load_zone, clear_binding, delete_zone.
Clearing a stale binding is best-effort — failure there is logged and the delete proceeds anyway.
list ¶
Return zones and buffers for the current project.
The server returns both static zones (dynamic=False) and
buffers (dynamic=True) in the same response. The SDK does
not filter client-side (the JS reference client does — RESEARCH §5
quirk #11 — and the SDK explicitly chooses not to).
Examples:
GroupsAPI ¶
Bases: ResourceAPI
List, create, update, and delete trackable groups.
list is defined last (class-scope shadow workaround for mypy).
M4 adds compound delete (cascade-detach) and add_tags /
remove_tags helpers.
delete_raw ¶
Delete a group (single REST call, no trackable-detach).
Advanced. :meth:delete (added in M4) first removes the
group from every trackable that referenced it. Calling
delete_raw on a group with members leaves dangling references
— only use it if you've already detached or know the server
cascades server-side.
delete ¶
Delete a group, first detaching every trackable that referenced it.
The server has no "tags in group X" endpoint per the JS reference
client (RESEARCH §4 "Delete group"), so the SDK lists trackables
and filters client-side, then calls replace_groups on each
member before the final DELETE /groups/{uid}.
Step names: load_member_tags, detach_from_tag,
delete_group.
Detach failures do not abort the cascade — the group is still
deleted afterward. If any tags couldn't be detached, the SDK
raises :class:PartialFailureError AFTER the group delete
succeeded, with the dangling tag uids in partial_result.
add_tags ¶
Add a group to each tag's membership, continue-on-error.
For each tag the SDK loads its current group list, appends
group_uid (no-op if already present), and writes the new
list. Failures land in BulkResult.failures.
remove_tags ¶
Remove a group from each tag's membership, continue-on-error.
list ¶
UsersAPI ¶
Bases: ResourceAPI
User-related calls.
M2 ships list, me, and get_access_control. Mutating
operations (create / update / delete / change_password) land in M3
and M5. list is defined last to avoid mypy's class-scope shadow
of the builtin list.
me ¶
Return the currently-authenticated user as a :class:User.
There is no server-side /users/me endpoint — this method is
composed: it calls GET /users/?withProjects=true, finds the
record whose email matches the SDK's stored auth email, and
returns it.
Raises:
| Type | Description |
|---|---|
RtlsError
|
If the current email is not present in the response (the server's user list excludes self for some reason — unusual but possible with scoped tokens). |
Examples:
get_access_control ¶
Return the current user's access-control entries.
Returns a list of per-company role assignments. M2 surfaces them
as raw dicts; a dedicated AccessControlEntry model lands
with the write surface in M3.
create_raw ¶
Create a user (single REST call, no project-access binding).
Advanced. :meth:create (added in M5) also binds the new
user to a project role in one compound call. Use create_raw
only if you need to skip the role-binding step.
update_raw ¶
Update a user (single REST call, no project-access binding).
Advanced. :meth:update (added in M5) also re-binds the
user's project role if you pass project_role=.
update_access ¶
Assign or change a user's role on a project.
Wraps POST /projects/access/. Used internally by M5's
:meth:create / :meth:update compounds to bind project roles
atomically; exposed here for callers that need it standalone.
restore_password ¶
Trigger a password-restore email for the given address.
Unauthenticated — the server only needs the email. Returns the server's response payload.
validate ¶
Server-side validation for user-form fields.
The wire shape varies by field ({"email": "..."},
{"password": "...", "uid": "...", "isOld": true}, etc.); the
SDK passes through whatever you give it. Returns the parsed
server response. M5's :meth:change_password compound calls
this internally.
change_password_raw ¶
Change a user's password (single REST call, no re-login).
Advanced. M5's :meth:change_password wraps this to also
re-authenticate the SDK afterwards. Calling change_password_raw
directly leaves the SDK holding a stale token — your next call
will trigger a 401 + re-login dance.
create ¶
Create a user and optionally bind them to a project role.
One call collapses two REST round-trips: POST /users plus
POST /projects/access/. If the role-binding step fails, the
SDK best-effort rolls back the user create — see
:class:PartialFailureError for the orphan uid if rollback
also fails.
project_role is skipped (no second call) when the created
user comes back as COMPANY_ADMIN: company admins have
implicit project access at the org level.
Step names: create_user, bind_project_role,
delete_user_rollback.
update ¶
Update a user, optionally re-binding their project role.
Self-edit detection: if uid matches the SDK's current user
AND the update changes email, the SDK refreshes the
in-memory auth state so subsequent X-User-Email headers
stay valid.
Step names: update_user, bind_project_role.
list_roles ¶
Return all valid roles the server accepts on user create / update.
Endpoint shape varies by deployment; the SDK best-effort parses
the response into :class:Role members. Used by
:meth:ContextAPI.load (M6). Returns an empty list if the
endpoint isn't available or the response shape isn't recognized.
list ¶
Return all users visible to the current scope.
with_projects=True asks the server to include per-user
project assignments inside each user record.
NodesAPI ¶
Bases: ResourceAPI
Read and (with care) mutate hardware nodes.
The JS reference client fetches nodes at company scope, not project
scope. The SDK doesn't auto-switch — call
client.with_scope(company_uid=...) if you need the company-wide
view. (with_scope lands in M8.)
M5 adds compound :meth:release (close-association + release in one
call, with per-call scope) and :meth:import_ (validate + create
loop). list is defined last (class-scope shadow workaround).
create ¶
Register a new node by its MAC address.
The MAC is the only required input. The server's createNode
schema accepts other fields (name, hw_info,
parent_mac_address, bridge_mac_address, announce,
node_type, periferial_info, uwb_trx_config,
company_uid, project_uid) via **fields, but the
canonical commissioning flow is MAC-only; metadata is added
later via :meth:update_raw.
The MAC is normalized to bare 12-hex lowercase (the server's
validation regex is /^[a-fA-F0-9]{12}$/ — no separators).
Six input formats accepted; malformed input raises
:class:ValueError before any HTTP call.
Examples:
delete_raw ¶
Delete a node (single REST call, fail-fast).
Advanced. :meth:delete (added in M5) is the
continue-on-error bulk variant.
validate ¶
Validate one or more MAC addresses against the server.
Returns the raw server response (typically containing valid,
invalid, duplicate lists). M5's :meth:import_ compound
wraps this.
assign_raw ¶
Assign a node to a project (single REST call).
Advanced. See M5's :meth:release for the inverse, which is
wrapped as a compound to avoid the JS client's scope-mutation bug.
release_raw ¶
Release a node from a project (single REST call).
Advanced. This does NOT close any open trackable-association
for the MAC first — :meth:release (M5) does both safely with
per-call scope.
release ¶
Release a node from a project, closing any open association.
The compound first looks up the open trackable-association for
mac_address and closes it; only then does it release the
node. All sub-calls run under a per-call scope override so the
target project travels in the request headers — the JS
reference client mutates axios.defaults here and leaks the
change on error (RESEARCH §4 "Release node and close its
association"). The SDK's per-call scope is per-thread and
reverts on exit.
Step names: close_association, release_node.
import_ ¶
Validate MAC addresses, then create each valid row.
Short-circuits if any MAC fails validation — no partial creates
when the input has known-bad rows. on_progress(done, total)
(optional) is invoked after each create attempt.
Returns:
| Type | Description |
|---|---|
ImportResult
|
Lists of valid / invalid / duplicate MACs, the number of nodes actually created, and per-row failures from the create step. |
delete ¶
Delete one or many nodes, continue-on-error.
Diverges from :meth:delete_raw (M3) which is fail-fast: this
compound continues past per-item failures and returns a
:class:BulkResult listing successes and failures separately.
send_param ¶
Queue a parameter write for a node via win_message_que.
Each kwarg becomes one entry in the plist. The server accepts
any subset in one call — common keys are sf, sync_slot,
nwk_cfg, srv_mode, tdoa_schedule, tdoa_schd,
p. Fire-and-forget on success; no response body is parsed.
MAC is normalised.
Examples:
read_params ¶
Read current parameter values for one or more nodes.
Builds repeated mac= and fields= query parameters
(matching the desktop client's wire format). force_update
bypasses the server-side cache and asks the gateway to ping
the node — slower; use sparingly.
The server caps a single hw_params request at 20 MAC
addresses, so larger inputs are split into batches of 20 — one
request per batch — and the results are concatenated (input
order preserved).
get ¶
Fetch a single node by uid.
The server returns {nodes: [n]} — a one-element list — even
for a single-uid GET; the SDK unwraps it. A missing uid (empty
wrapper or HTTP 404) raises :class:NotFound.
list ¶
List nodes, optionally filtered by MAC address or by uid.
Three mutually-exclusive modes:
- no filter —
GET /api/v2/nodes(every node in scope); macs—GET /api/v2/nodes?mac=<m1>&mac=<m2>(each MAC normalized to bare 12-hex lowercase, matching the server'smac_regex);uids—GET /api/v2/nodes?uid=<u1>&uid=<u2>.
Passing both macs and uids raises :class:ValueError
before any HTTP call. A single string is accepted as shorthand
for a one-element list. An empty list is treated as no filter.
Examples:
AnchorsAPI ¶
Bases: ResourceAPI
List, create, update, and delete WIN anchors in the current scope.
Anchors are present only when the WIN module is enabled on the
server. Some deployments return 404 on the list endpoint — the SDK
raises :class:NotFound.
Server-side anchor deletion closes open anchor associations
automatically; the SDK does not pre-emptively call
:meth:TagAssociationsAPI.close.
list is defined last (class-scope shadow workaround).
create ¶
Create a placed anchor (WIN beacon).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Display name. Required. |
required |
position
|
dict[str, Any]
|
Placement object. Must include |
required |
type
|
str
|
|
'ANCHOR'
|
**fields
|
Any
|
Optional pass-through fields the server schema permits:
|
{}
|
Examples:
>>> anchor = client.anchors.create(
... name="Anchor 7",
... position={"x": 1.0, "y": 2.0, "z": 0.0, "area_uid": area.uid},
... )
>>> anchor.uid
'NELkj…'
Notes
win_anchor does NOT carry a mac_address field. If you
want to bind a piece of hardware (with a MAC) to this anchor,
also call :meth:NodesAPI.create and
:meth:TagAssociationsAPI.create (or use the planned
anchors.create_with_node compound when it lands).
update ¶
Update an anchor's fields.
Free-form **fields to match the server's permissive
update_win_anchor schema. Validates type when present
(must be ANCHOR or BRIDGE). Does NOT validate
position structure — caller is responsible for shape.
delete ¶
Delete an anchor.
Server-side closes any open anchor associations referencing it;
the SDK does NOT pre-emptively call anchor_associations.close.
get ¶
Fetch a single anchor by uid.
The server returns {win_anchors: [a]} — a one-element list —
even for a single-uid GET; the SDK unwraps it. A missing uid
(empty wrapper or HTTP 404) raises :class:NotFound.
AnchorAssociationsAPI ¶
Bases: ResourceAPI
List, create, update, close, and delete node ↔ anchor bindings.
list() returns only open associations; list_all() returns
open plus closed.
The server's close endpoint identifies the association by
mac OR win_uid in the body — not by the
association's own uid. :meth:close mirrors the trackable
TagAssociationsAPI.close shape so callers familiar with that one
feel at home.
list is defined last (class-scope shadow workaround).
create ¶
Create an anchor association (bind a MAC to a placed anchor).
The MAC is normalized to bare 12-hex lowercase before sending
(server validates /^[a-fA-F0-9]{12}$/) and travels on the
wire as mac_address; win_uid is sent verbatim.
open ¶
Open a fresh anchor association binding mac to win_uid.
Enforces a clean 1:1 binding: any currently-open association for
this hardware mac or for this anchor win_uid is
closed first, then a new association is created. This stops a
node or a placed anchor from ending up bound twice.
Rather than listing every association to find collisions, this
asks the server to close by mac and by win_uid directly
and treats "nothing to close" as a no-op — one fewer round-trip
and no list/close race. Any other error (auth, 5xx, …) still
propagates.
Returns the newly-created :class:AnchorAssociation.
update ¶
Rebind an existing association.
Pass mac to point the association at a different
hardware node, or win_uid to point it at a different
placed anchor. At least one must be supplied. The association
uid is unchanged by this call.
Server contract: the PUT body MUST carry both mac
and win_uid (the server rejects partial bodies with a 422
"mac" is required / "win_uid" is required).
When the caller supplies only one field, the SDK does one extra
list_all() lookup to fill the unchanged value. Pass both
explicitly to avoid the lookup.
close ¶
Close an open anchor association.
The endpoint is PUT /win_anchor_associations/close and the
server identifies the record by mac_address OR win_uid
in the body — not by the association's own uid.
Three accepted call shapes (parallel to
:meth:TagAssociationsAPI.close):
close(association)— pass the :class:AnchorAssociationobject returned by :meth:create/ :meth:list; the SDK picksmacorwin_uidfrom it.close(association_uid)— pass the association's own uid; the SDK does one extralist_all()to resolve the identifying fields.close(mac=..., win_uid=...)— pass either identifier explicitly.
TagAssociationsAPI ¶
Bases: ResourceAPI
Read trackable ↔ MAC associations.
Two endpoints surface here:
- :meth:
listreads currently-open associations. - :meth:
list_active_in_periodreads associations active in a window; uses a distinct endpoint path (/track_assoc) and — uniquely among RTLS endpoints — acceptsstart/endas epoch seconds, not milliseconds (RESEARCH §4).
list is defined last (class-scope shadow workaround).
list_active_in_period ¶
Return associations active during [start, end].
The SDK converts datetime to epoch seconds for this endpoint.
Pass timezone-aware datetimes; naive values raise ValueError.
create ¶
Create a trackable-association (bind a MAC to a tag).
mac is normalized to bare 12-hex lowercase before sending
(server validates /^[a-fA-F0-9]{12}$/) and travels on the
wire as mac_address; obj_uid (the trackable's uid) is
sent verbatim.
Note: this binds a node (by MAC) to a TAG (obj_uid). To bind
a node to a placed ANCHOR, use
:meth:AnchorAssociationsAPI.create (client.anchor_associations)
— it talks to /win_anchor_associations and uses win_uid
on the wire.
open ¶
Open a fresh trackable-association binding mac to obj_uid.
Enforces a clean 1:1 binding: any currently-open association for
this hardware mac or for this obj_uid (trackable) is
closed first, then a new association is created. This stops a node
or a tag from ending up bound twice.
Mirrors :meth:AnchorAssociationsAPI.open: rather than listing
every association to find collisions, it asks the server to close
by mac and by obj_uid directly and treats "nothing to
close" as a no-op — one fewer round-trip and no list/close race.
Any other error (auth, 5xx, …) still propagates.
Returns the newly-created :class:TagAssociation.
close ¶
Close an open association.
The endpoint is PUT /trackable_objects_associations/close and
the server identifies the association by mac_address OR
obj_uid (the trackable's uid) in the body — not by the
association's own uid (the server's close-handler reads only
req.body.mac_address and req.body.obj_uid). M14 regression
— earlier close(uid) sent {"uid": "..."} and the server
replied "No mac or trackable object uid specified.".
Three accepted call shapes:
close(association)— pass the :class:TagAssociationobject returned by :meth:create/ :meth:list; the SDK picksmacorobj_uidfrom it.close(association_uid)— pass the association's own uid; the SDK does one extralist()to resolve the identifying fields.close(mac=..., obj_uid=...)— pass either identifier explicitly.
bulk_create ¶
Create many associations, one at a time, continue-on-error.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pairs
|
list[tuple[str, str]]
|
List of |
required |
Returns:
| Type | Description |
|---|---|
BulkResult[TagAssociation]
|
|
AlarmsAPI ¶
Bases: ResourceAPI
Read alarm events and (clear) existing alarms.
Three time-bounded reads:
- :meth:
list_active— currently-open alarms. - :meth:
list_for_site— alarms in a site over a window. - :meth:
list_for_area— alarms in a single sublocation over a window.
The CSV export variants of these endpoints are deferred to the
reports surface in M7 (client.reports.alarms_csv).
list_for_site ¶
Return alarms raised at site_uid in [start, end].
alarm_types filters to specific alarm-type names (e.g.
["zone_violation", "buffer_violation"]); None lists all.
Timestamps are converted to epoch milliseconds per the server's
contract (RESEARCH §2). Naive datetime raises ValueError.
list_for_area ¶
Return alarms raised in a sublocation over a window.
Same conventions as :meth:list_for_site; the scoping parameter
is sublocation_uid instead of location_uid.
update ¶
Update / clear an alarm.
Uses PUT despite the public docs saying POST — the JS reference client uses PUT and the server accepts only PUT (RESEARCH Discrepancy #3).
EventsAPI ¶
Bases: ResourceAPI
Read zone events and external (PWS) events.
iter_pws and pages are lazy: HTTP requests fire only when
the iterator advances past the current page. This keeps large
windows out of memory and lets callers break early.
list_zone_events ¶
Return zone events for area_uids in [start, end].
The server returns a paginated response; this method reads only the first page. Callers needing the full set should use the iterator surface.
iter_pws ¶
Lazy iterator over PWS events for one or more trackables.
Yields one :class:PwsEvent per server row. HTTP requests fire
only when the iterator advances past the current page — calling
list(client.events.iter_pws(...)) materialises everything,
while next(iter) fetches the minimum needed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trackable_uid
|
str | list[str]
|
One uid string or a list of uids (Rails-style
|
required |
start
|
datetime
|
Tz-aware datetimes. Naive datetimes raise |
required |
end
|
datetime
|
Tz-aware datetimes. Naive datetimes raise |
required |
page_size
|
int
|
Server-side limit hint. The server may return fewer rows. |
1000
|
Examples:
pages ¶
Iterate one :class:Page at a time — for progress reporting.
Same conventions as :meth:iter_pws, but yields whole pages
carrying page_number / total_count / has_next so
callers can print progress without counting items themselves.
ReportsAPI ¶
Bases: ResourceAPI
Generate and read reports.
M2 shipped read-only access (list, get). M7 adds the
compound flows: heatmap polling, PWS aggregation, CSV downloads
(zone-activity, alarms).
list is defined last (class-scope shadow workaround for mypy).
heatmap ¶
Generate a heatmap report — POST, then poll until ready.
Uses the live /api/v2/obsolete/report paths (RESEARCH
Discrepancy #5). Default timeout=None is unbounded; pass a
finite value to bound the wait. On timeout the SDK raises
:class:ReportTimeout carrying the in-flight report_uid so
the caller can resume polling via :meth:get later.
On a server-reported error the SDK raises :class:ReportFailed.
Examples:
pws ¶
Aggregate all PWS-event pages into a :class:PwsReport.
Holds the full result set in memory. For large windows prefer
:meth:EventsAPI.iter_pws (lazy).
zone_activity_csv ¶
Download Zone-Activity CSV, following content-next-page chunks.
With stream_to=None (default) the SDK accumulates in memory
and returns a :class:CsvBlob. With stream_to=<file-like>
chunks are written as they arrive and the return is None.
Duplicate header rows in chunks 2..N are stripped by default
(dedup_headers=True) so the concatenated bytes parse as one
coherent CSV.
alarms_csv ¶
Download Alarms CSV with an optional multi-line banner prefix.
The banner mirrors the JS reference client's behaviour
(#Site / #From / #To lines) so downstream tooling can
identify the report. Pass include_banner=False for the
bare server CSV.
list ¶
List reports of type generated for site_uid in a window.
Timestamps are sent as ISO-8601 strings (RESEARCH §2 — reports
diverge from alarms here). Naive datetime raises ValueError.
NotificationsAPI ¶
Bases: ResourceAPI
Read and manage notification profiles + notification-type metadata.
list is defined last (class-scope shadow workaround).
SubscribersAPI ¶
Bases: ResourceAPI
Read and manage alarm subscribers and system-notification subscribers.
list is defined last (class-scope shadow workaround).
create ¶
Create an alarm subscriber.
fields typically contains addresses ({"email": [...],
"phone": [...]}) and a notifications list.
update_system_notifications ¶
Update a user's system-event subscriptions.
System-event subscriptions live on a separate endpoint
(/system_notifications/) with a different envelope shape
(RESEARCH §3 Subscriber normalization).
list_system_notification_subscribers ¶
List system-event subscribers (distinct from alarm subscribers).
The server's envelope here is systemNotificationSubscriber
(singular noun, list value) — RESEARCH §3 Subscriber normalization.
ProjectsAPI ¶
Bases: ResourceAPI
List, create, update, and delete projects.
list is defined last (class-scope shadow workaround).
create ¶
Create a new project.
Server's Joi createProject schema expects the body nested
under a top-level project key:
{"project": {"name": "...", ...}}. The SDK wraps the body so
callers can pass flat kwargs. M14 regression — phase-1 failure
was the server returning "project" is required.
update ¶
Update a project's fields.
Body is nested under project to match
:meth:create / the Joi updateProject schema.
get ¶
Fetch a single project by uid.
The server exposes no single-project endpoint — a path GET
(/projects/{uid}) returns 404 and the list endpoint ignores
a ?uid= filter — so this fetches the projects visible to the
current user via :meth:list and selects the match client-side.
Raises :class:NotFound if no visible project has that uid.
list ¶
Return projects visible to the current user.
Project listing does not require an active X-User-Project header
(RESEARCH §1 Projects row 1) — it's commonly the first scoped call
after login.
CompaniesAPI ¶
Bases: ResourceAPI
List, create, update, and delete companies.
list is defined last (class-scope shadow workaround).
create ¶
Create a new company and seed it with an admin user.
Per RESEARCH §1, POST /companies/ accepts a body like
{company: {...}, user: {email, password, role}} — the SDK
normalises that envelope shape so callers don't have to.
The seeded admin's role is server-required (Joi
companyCreate.user.role). It defaults to "company_admin",
the only role that makes sense for a freshly-seeded tenant.
get ¶
Fetch a single company by uid.
The server exposes no single-company endpoint — a path GET
(/companies/{uid}) returns 404 and the list endpoint ignores
a ?uid= filter — so this fetches the companies visible to the
current user via :meth:list and selects the match client-side.
Raises :class:NotFound if no visible company has that uid.
list ¶
Return companies visible to the current user.
On most deployments this returns exactly one company; super-admin users may see all companies.
TDOA configuration¶
SchedulesAPI ¶
Bases: ResourceAPI
List, create, update, and delete TDOA schedules in the current project.
A schedule is a named per-slot timing plan; the per-slot table is
carried as an opaque content blob (see :class:Schedule).
Active-schedule selection lives on :class:LsbAPI (via
client.lsb), not here.
create ¶
Create a schedule.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Display name. |
required |
content
|
str | None
|
Serialized per-slot table. Format depends on |
None
|
default
|
bool
|
Mark this schedule as the project default. |
False
|
tdoa_version
|
int
|
Schedule format version (1 or 2). Defaults to 2. |
2
|
**fields
|
Any
|
Additional fields the deployment supports — forwarded as-is. |
{}
|
LsbAPI ¶
Bases: ResourceAPI
Read/write the project's TDOA / LSB sync configuration and drive sync.
LSB settings define the sync topology (masters, distributors, slot
assignments, superframe frequency, active schedule). :meth:start /
:meth:stop push that configuration to the radios and :meth:status
reports the running state. v2 only.
get ¶
Fetch the LSB settings for the current project scope.
The server returns tdoa_version and lsb_version at the
top level of the response and the rest of the record wrapped
under tdoa_settings. The SDK merges them into a single
:class:LsbSettings instance.
save ¶
Save an :class:LsbSettings (create-or-update).
The server upserts on POST keyed by project_uid in the body;
there is no separate create endpoint.
The nested values blob the server expects is rebuilt from the
settings' typed members (the scalars plus the masters /
distributors lists) — the members are the source of truth.
tdoa_version / lsb_version also travel at the top level
when set.
calc_tdoas ¶
Compute tdoas (TDOA slot count) from a schedule.
Returns max(slot) + 1 over the schedule's rows. When
schedule is omitted, the project's active schedule is loaded
from the current settings. Pass an already-fetched
:class:Schedule to avoid re-reading it.
Raises :class:RuntimeError if there is no active schedule, the
content is empty, or it is a v1 (#version 2) schedule.
send_schedule ¶
Push the schedule to each participating anchor over the gateway.
Parses the schedule, joins each row's source / destination anchor
to its node + geometry, and sends a per-anchor tdoa_schedule
plist. When schedule is omitted, the project's active schedule
is loaded from the current settings; pass an already-fetched
:class:Schedule to avoid re-reading it.
status ¶
Report the running state of LSB sync.
Reads the sf parameter of every node (cached) to find the
nodes currently reporting as master ("real masters"), and
compares them against the masters configured in the settings:
- no real masters → :attr:
LsbStatus.STOPPED - real masters == configured masters → :attr:
LsbStatus.STARTED - anything else → :attr:
LsbStatus.UNKNOWN
start ¶
start(*, timeout=_DEFAULT_TIMEOUT, check_interval=_DEFAULT_CHECK_INTERVAL, retries=_DEFAULT_SEND_RETRIES)
Start LSB sync from the saved configuration.
Stops any running masters, computes tdoas (from the active
schedule) and bcs (from configured slots), pushes sf to
each configured master, sends the schedule, then pushes
sync_slot. Polls :meth:status until it reports
:attr:LsbStatus.STARTED or timeout elapses.
Returns True if sync reached the started state.
Raises :class:RuntimeError if there are no configured masters,
no active schedule, or a master's anchor has no bound node with a
node_id.
stop ¶
Stop LSB sync.
First marks the project as not-configured (configured_state =
False) and persists it, then zeroes the sf of every node
currently reporting as master, re-checking after a pause until
none remain or timeout elapses.
Returns True if no node is reporting as master afterwards.
NetworkAPI ¶
Bases: ResourceAPI
Read and write the project's network settings.
Today the payload is the service-mode toggle (enable / disable / duration / device list). The desktop client uses POST for both create and update; the SDK mirrors that.
update ¶
Create or update the project's network settings.
Passing service_mode enables / disables / extends the
per-device service-mode flag. Pass other supported fields as
kwargs — they're forwarded as-is.
Identity & system¶
AuthAPI ¶
Bases: ResourceAPI
User-facing auth helpers — capabilities probe and whoami.
Note that the active SDK token is not exposed here. Mutating operations (login, logout, change-password) land in M5.
whoami ¶
Return the currently-authenticated user.
Delegates to :meth:UsersAPI.me — composed via a search of
GET /users?withProjects=true for the row matching the SDK's
stored auth email. See UsersAPI.me for details.
capabilities ¶
Probe whether SSO is configured + infer the API version.
Calls GET /auth/sso/log_in. A successful response means SSO is
configured (sso=True, api_version="2.1"); any
:class:RtlsError from the call means SSO is unavailable
(sso=False, api_version="2.0"). This mirrors the JS
reference client's "rejection-is-the-signal" heuristic
(RESEARCH §4 "Probe SSO support").
change_password ¶
Change the current user's password and re-authenticate the SDK.
Four steps: validate the old password, validate the new
password, PUT /users/changePassword, then clear the cached
token and force a fresh login with the new credentials. After a
successful run, the SDK's stored password is the new one.
Step names: validate_old, validate_new,
change_password, reauthenticate.
If the re-authentication step fails (server accepted the change
but the new login can't proceed), the SDK raises
:class:AuthenticationError with a message explaining that the
in-memory state is now stale. The caller should reconstruct the
client with the new password.
SystemAPI ¶
Bases: ResourceAPI
Host info, uptime, connections, and server version metadata.
The aggregated :meth:health view (fan-out across host + uptime +
connections) is implemented in M5; M2 only exposes the individual
accessors.
version ¶
Return server version info from GET /version.
Note: /version is one of the few endpoints not under the
/api/v2/ prefix.
health ¶
Aggregate host + uptime + connections in one snapshot.
Each sub-call is independent: a failure on one sets that field
to None and records the exception in
SystemHealth.errors, leaving the rest of the snapshot
intact. partial=True when any sub-call failed.
Sequential in v1 (sync SDK); a v2 async surface would fan these out in parallel.
ws_host ¶
Return the WebSocket hostname the server expects clients to dial.
This is the single REST hop needed before opening a WS connection (deferred to v2).
helper_link ¶
URL of the help portal (deployment-specific).
Returned by GET /info/helper. Body shape is loose across
deployments; this method surfaces whichever URL-shaped string
the server returned, or None if absent. Used by
:meth:ContextAPI.load (M6).
monitor_link ¶
URL of the site-monitor portal (deployment-specific).
Returned by GET /info/site_monitor. Same loose-shape
conventions as :meth:helper_link.
MessagingAPI ¶
Bases: ResourceAPI
Send messages to trackable badges / users.
send ¶
Send a message addressed to one or more recipients.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
from_uid
|
str
|
The sender's uid (typically the current user). |
required |
to_uids
|
list[str]
|
Recipient uids — trackables, users, or device uids depending on what your deployment routes via messaging. |
required |
body
|
str
|
The message text. |
required |
Returns:
| Type | Description |
|---|---|
dict
|
The server response. M3 surfaces it as a raw dict because the wire shape isn't documented stably (RESEARCH §1 only says "messages: []"); a typed model can be added later without breaking callers that already treat the return as opaque. |
LoggerAPI ¶
Bases: ResourceAPI
Get / set / clear the server's runtime logger filter (admin).
The JS reference client treats this endpoint as no-auth-required; the SDK relies on the configured client identity. Filters are arbitrary key/value pairs whose semantics live server-side.
Streaming¶
WsAPI ¶
Bases: ResourceAPI
WebSocket subscription surface (client.ws).
list_channels ¶
Return the channel names the server advertises.
One-shot dial; not authenticated server-side (session.check
is bypassed for the get method).
subscribe ¶
Open a WS session and subscribe to channels.
Returns a :class:WsSession — context manager and blocking
iterator of :class:WsChannelEnvelope instances (typed channel
models for known channels, :class:WsChannelMessage for
unknown / decode-failed channels). Raises :class:WsAuthError
synchronously if the server rejects the initial subscribe.
include_heartbeats (default False) suppresses
notify frames with action == "HB". Set to True to
receive them as :class:NotifyMessage instances.
Session context¶
ContextAPI ¶
Bases: ResourceAPI
Aggregated session bootstrap.
The single :meth:load method replaces the JS reference client's
18-call loadEntities saga (RESEARCH §4) — DESIGN's highest-value
compound collapse.
load ¶
Hydrate the deployment view under the current scope.
Sub-resources are fetched sequentially in a stable order. Each
sub-call's failure goes into :attr:SessionContext.errors
keyed by a stable name; the rest of the snapshot still
populates. :attr:SessionContext.partial is True when any
sub-call failed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
scope
|
str | None
|
|
None
|
Examples: