Download OpenAPI specification:Download
TrakRF public REST API. See /api for the customer-facing reference.
Spec available as YAML (/api/openapi.yaml) and JSON (/api/openapi.json).
HTTP method coverage (HEAD, OPTIONS, 405 / Allow): /api/http-method-coverage
Surrogate ID width: declared format: int64 on the wire so SDK regeneration does not break when the ID namespace eventually outgrows int32. Service-side ID generation stays within int32 (2^31-1) during v1; values above that bound are rejected with 400 validation_error / too_large. The wider wire type is a long-horizon contract, not a claim that current values exceed int32.
Nullable field interpretation: OpenAPI 3.0's nullable: true keyword is interpreted differently across codegen targets. Verified-working: openapi-typescript@7.x emits string | null correctly, and the openapi-generator-cli python target emits Optional[StrictStr] correctly — both round-trip CRUD against null-bearing responses unmodified. Known-broken: datamodel-codegen@0.57.0 emits nullable: true read-shape fields as non-Optional required fields, so Pydantic validation fails on every nullable field that is actually null. Integrators using datamodel-codegen should either switch to the openapi-generator-cli python target or apply --use-annotated --use-union-operator with a custom post-processing pass.
Required scope: assets:read
Paginated assets list with natural-key filters, sort, and substring search.
Default scope returns currently-effective assets only — rows whose valid_from is in the past AND whose valid_to is null or in the future. The is_active filter is independent of temporal validity; omit it to include both active and inactive rows within the effective window, or pass ?is_active=true/false to filter further.
| limit | integer [ 1 .. 200 ] Default: 50 max 200 |
| offset | integer [ 0 .. 2147483647 ] Default: 0 min 0 |
| location_id | Array of integers <int64> [ items <int64 > [ 1 .. 2147483647 ] ] Default: "" filter by current location id (canonical, may repeat); mutually exclusive with location_external_key (400 ambiguous_fields if both supplied) |
| location_external_key | Array of strings[ items^[A-Za-z0-9-]+$ ] Default: "" filter by current location external_key (may repeat); mutually exclusive with location_id (400 ambiguous_fields if both supplied) |
| external_key | Array of strings[ items^[A-Za-z0-9-]+$ ] Default: "" filter by asset external_key, equality match (may repeat for any-of) |
| is_active | boolean filter by active flag |
| include_deleted | boolean Default: false when true, include soft-deleted rows in the response. deleted_at is populated for those rows. Orthogonal to is_active. |
| q | string^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$ substring search (case-insensitive) on name, external_key, description, and active tag values |
| sort | string^(|(?:external_key|-external_key|name|-name|c... Default: "" comma-separated; prefix '-' for DESC |
{- "data": [
- {
- "created_at": "2025-04-29T12:34:56.000Z",
- "deleted_at": "2025-04-29T12:34:56.000Z",
- "description": "string",
- "external_key": "string",
- "id": 0,
- "is_active": true,
- "location_external_key": "string",
- "location_id": 0,
- "metadata": { },
- "name": "string",
- "tags": [
- {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
], - "updated_at": "2025-04-29T12:34:56.000Z",
- "valid_from": "2025-04-29T12:34:56.000Z",
- "valid_to": "2025-04-29T12:34:56.000Z"
}
], - "limit": 50,
- "offset": 0,
- "total_count": 100
}Required scope: assets:write
Create a new asset record, optionally with one or more tags (RFID, BLE, barcode).
The external_key field is optional. Provide a value from your system of record
(ERP, WMS, asset management) for natural-key joins, or omit it to receive a
server-assigned external_key in the format ASSET-NNNN (per-organization sequence).
A caller-supplied external_key that collides with an existing asset returns 409.
Returns the created asset with its assigned tags. The Location response header contains the canonical URL.
Asset to create with optional tags
| description | string or null [ 1 .. 1024 ] characters ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$ |
| external_key | string [ 1 .. 255 ] characters ^[A-Za-z0-9-]+$ external_key is optional. Omit to receive a server-assigned key in the format ASSET-NNNN (per-organization sequence). When supplied, must satisfy the external_key_pattern. |
| is_active | boolean |
object | |
| name required | string [ 1 .. 255 ] characters ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$ |
Array of any or null (TagRequest) | |
| valid_from | string <date-time> |
| valid_to | string or null <date-time> |
{- "description": "Main warehouse forklift",
- "external_key": "forklift-3",
- "is_active": true,
- "metadata": {
- "property1": null,
- "property2": null
}, - "name": "Forklift 3",
- "tags": [
- {
- "tag_type": "rfid",
- "value": "string"
}
], - "valid_from": "2025-01-01T00:00:00Z",
- "valid_to": "2026-01-01T00:00:00Z"
}{- "data": {
- "created_at": "2025-04-29T12:34:56.000Z",
- "deleted_at": "2025-04-29T12:34:56.000Z",
- "description": "string",
- "external_key": "string",
- "id": 0,
- "is_active": true,
- "location_external_key": "string",
- "location_id": 0,
- "metadata": { },
- "name": "string",
- "tags": [
- {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
], - "updated_at": "2025-04-29T12:34:56.000Z",
- "valid_from": "2025-04-29T12:34:56.000Z",
- "valid_to": "2025-04-29T12:34:56.000Z"
}
}Required scope: assets:write
Delete an asset by its canonical id. The asset is removed from all subsequent queries and its external_key becomes immediately available for reuse. Returns 204 on success, 404 if the asset does not exist or has already been deleted.
| asset_id required | integer <int64> [ 1 .. 2147483647 ] Asset id (canonical) |
{- "error": {
- "detail": "string",
- "fields": [
- {
- "code": "required",
- "field": "string",
- "message": "string",
- "params": {
- "property1": null,
- "property2": null
}
}
], - "instance": "string",
- "request_id": "string",
- "status": 0,
- "title": "string",
- "type": "validation_error"
}
}Required scope: assets:read
Retrieve an asset by its canonical id. Returns 404 if the asset does not exist.
Path-addressed retrieval bypasses the temporal-validity filter applied on list endpoints — any non-deleted asset is returned regardless of its valid_from / valid_to values. Use this endpoint when you have an id and need the row even if its effective window has elapsed.
| asset_id required | integer <int64> [ 1 .. 2147483647 ] Asset id (canonical) |
{- "data": {
- "created_at": "2025-04-29T12:34:56.000Z",
- "deleted_at": "2025-04-29T12:34:56.000Z",
- "description": "string",
- "external_key": "string",
- "id": 0,
- "is_active": true,
- "location_external_key": "string",
- "location_id": 0,
- "metadata": { },
- "name": "string",
- "tags": [
- {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
], - "updated_at": "2025-04-29T12:34:56.000Z",
- "valid_from": "2025-04-29T12:34:56.000Z",
- "valid_to": "2025-04-29T12:34:56.000Z"
}
}Required scope: assets:write
Apply a JSON Merge Patch (RFC 7396) to an asset. Only fields included in the request body are changed; fields set to null clear the corresponding nullable column. Omitted fields are left unchanged. An empty body ({}) is a no-op and returns the current resource unchanged. Read-only fields are uniformly governed by the accept-if-matches, reject-if-differs rule: a value matching the current resource state is silently normalized out (so a verbatim GET → PATCH round-trip succeeds without manual scrubbing), and a differing value returns 400 with code: read_only. This applies to the server-managed surrogate id + timestamps (id, created_at, updated_at, deleted_at), the tags collection, and the natural-key reference fields (external_key, location_id, location_external_key). Mutate external_key via POST /assets/{asset_id}/rename; asset location is collected through scan event ingestion (fixed-reader MQTT pipeline or handheld UI submission) and is not directly settable through the public API; mutate tags via POST /assets/{asset_id}/tags and DELETE /assets/{asset_id}/tags/{tag_id}.
| asset_id required | integer <int64> [ 1 .. 2147483647 ] Asset id (canonical) |
Fields to merge-patch
| description | string or null [ 1 .. 1024 ] characters ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$ |
| is_active | boolean |
object | |
| name | string [ 1 .. 255 ] characters ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$ |
| valid_from | string <date-time> |
| valid_to | string or null <date-time> |
{- "description": "Updated description",
- "is_active": true,
- "metadata": {
- "property1": null,
- "property2": null
}, - "name": "Forklift 3",
- "valid_from": "2025-01-01T00:00:00Z",
- "valid_to": "2026-01-01T00:00:00Z"
}{- "data": {
- "created_at": "2025-04-29T12:34:56.000Z",
- "deleted_at": "2025-04-29T12:34:56.000Z",
- "description": "string",
- "external_key": "string",
- "id": 0,
- "is_active": true,
- "location_external_key": "string",
- "location_id": 0,
- "metadata": { },
- "name": "string",
- "tags": [
- {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
], - "updated_at": "2025-04-29T12:34:56.000Z",
- "valid_from": "2025-04-29T12:34:56.000Z",
- "valid_to": "2025-04-29T12:34:56.000Z"
}
}Required scope: tracking:read
Location history for an asset identified by its canonical id.
The asset existence check follows path-addressed semantics — the asset is returned even if its valid_to has elapsed. Each history row's location reference applies the temporal-validity predicate, so an event referencing a location whose effective window is past surfaces with null location_external_key.
| asset_id required | integer <int64> [ 1 .. 2147483647 ] Asset id (canonical) |
| limit | integer [ 1 .. 200 ] Default: 50 max 200 |
| offset | integer [ 0 .. 2147483647 ] Default: 0 min 0 |
| from | string <date-time> Example: from=2025-04-29T12:34:56.000Z RFC 3339 start timestamp |
| to | string <date-time> Example: to=2025-04-29T12:34:56.000Z RFC 3339 end timestamp |
| sort | string^(|(?:event_observed_at|-event_observed_at)(?... Default: "" comma-separated; prefix '-' for DESC |
{- "data": [
- {
- "duration_seconds": 0,
- "event_observed_at": "2025-04-29T12:34:56.000Z",
- "location_external_key": "string",
- "location_id": 0
}
], - "limit": 50,
- "offset": 0,
- "total_count": 100
}Required scope: assets:write
Mutate the asset's external_key (natural / join key). This operation is destructive to downstream joins: any external system that has cached or indexed records on the old external_key will silently disconnect. Prefer a coordinated cutover with downstream consumers.
external_key is immutable via PATCH; this operation is the only way to change it. Distinct from a regular PATCH in audit logs (different URL surface).
The response includes descendant_count_affected for shape uniformity with the location rename verb. Assets have no hierarchy, so this value is always 0; it is emitted to save typed-client consumers from branching on rename verb.
| asset_id required | integer <int64> [ 1 .. 2147483647 ] Asset id (canonical) |
New external_key
| external_key required | string [ 1 .. 255 ] characters ^[A-Za-z0-9-]+$ |
{- "external_key": "ASSET-0042"
}{- "data": {
- "created_at": "2025-04-29T12:34:56.000Z",
- "deleted_at": "2025-04-29T12:34:56.000Z",
- "description": "string",
- "external_key": "string",
- "id": 0,
- "is_active": true,
- "location_external_key": "string",
- "location_id": 0,
- "metadata": { },
- "name": "string",
- "tags": [
- {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
], - "updated_at": "2025-04-29T12:34:56.000Z",
- "valid_from": "2025-04-29T12:34:56.000Z",
- "valid_to": "2025-04-29T12:34:56.000Z"
}, - "descendant_count_affected": 0
}Required scope: assets:write
Attach a tag (RFID EPC, BLE beacon ID, barcode, etc.) to an existing asset. The tag must be unique within the organization.
| asset_id required | integer <int64> [ 1 .. 2147483647 ] Asset id (canonical) |
Tag to attach
| tag_type required | string Discriminator for the polymorphic Tag resource. |
| value required | string [ 1 .. 255 ] characters ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$ |
{- "tag_type": "barcode",
- "value": "string"
}{- "data": {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
}Required scope: assets:write
Detach a tag from an asset by its tag record id. First successful removal returns 204; repeated calls return 404 — consistent with top-level resource DELETE semantics. The cross-asset / cross-org case (a tag that exists but is not attached to this asset, or belongs to a different org) also surfaces as 404.
| asset_id required | integer <int64> [ 1 .. 2147483647 ] Asset id (canonical) |
| tag_id required | integer <int64> [ 1 .. 2147483647 ] Tag id |
{- "error": {
- "detail": "string",
- "fields": [
- {
- "code": "required",
- "field": "string",
- "message": "string",
- "params": {
- "property1": null,
- "property2": null
}
}
], - "instance": "string",
- "request_id": "string",
- "status": 0,
- "title": "string",
- "type": "validation_error"
}
}Required scope: locations:read
Paginated locations list with natural-key filters, sort, and substring search.
Default scope returns currently-effective locations only — rows whose valid_from is in the past AND whose valid_to is null or in the future. The is_active filter is independent of temporal validity; omit it to include both active and inactive rows within the effective window, or pass ?is_active=true/false to filter further.
| limit | integer [ 1 .. 200 ] Default: 50 max 200 |
| offset | integer [ 0 .. 2147483647 ] Default: 0 min 0 |
| parent_id | Array of integers <int64> [ items <int64 > [ 1 .. 2147483647 ] ] Default: "" filter by parent id (canonical, may repeat); mutually exclusive with parent_external_key (400 ambiguous_fields if both supplied) |
| parent_external_key | Array of strings[ items^[A-Za-z0-9-]+$ ] Default: "" filter by parent's external_key (may repeat); mutually exclusive with parent_id (400 ambiguous_fields if both supplied) |
| external_key | Array of strings[ items^[A-Za-z0-9-]+$ ] Default: "" filter by location external_key, equality match (may repeat for any-of) |
| is_active | boolean filter by active flag |
| include_deleted | boolean Default: false when true, include soft-deleted rows in the response. deleted_at is populated for those rows. Orthogonal to is_active. |
| q | string^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$ substring search (case-insensitive) on name, external_key, description, and active tag values |
| sort | string^(|(?:external_key|-external_key|name|-name|c... Default: "" comma-separated, prefix '-' for DESC |
{- "data": [
- {
- "created_at": "2025-04-29T12:34:56.000Z",
- "deleted_at": "2025-04-29T12:34:56.000Z",
- "description": "string",
- "external_key": "string",
- "id": 0,
- "is_active": true,
- "name": "string",
- "parent_external_key": "string",
- "parent_id": 0,
- "tags": [
- {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
], - "updated_at": "2025-04-29T12:34:56.000Z",
- "valid_from": "2025-04-29T12:34:56.000Z",
- "valid_to": "2025-04-29T12:34:56.000Z"
}
], - "limit": 50,
- "offset": 0,
- "total_count": 100
}Required scope: locations:write
Create a new location in the hierarchy, optionally with one or more tags. Set parent_id (canonical) or parent_external_key (alternate) to nest under an existing parent.
The external_key field is optional. Provide a value from your system of record
(ERP, WMS, layout/plan) for natural-key joins, or omit it to receive a
server-assigned external_key in the format LOC-NNNN (per-organization sequence).
A caller-supplied external_key that collides with an existing location returns 409.
Location to create with optional tags
| description | string or null [ 1 .. 1024 ] characters ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$ |
| external_key | string [ 1 .. 255 ] characters ^[A-Za-z0-9-]+$ external_key is optional. Omit to receive a server-assigned key in the format LOC-NNNN (per-organization sequence), parallel to assets' ASSET-NNNN behavior. When supplied, must satisfy the external_key_pattern. |
| is_active | boolean |
| name required | string [ 1 .. 255 ] characters ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$ |
| parent_external_key | string or null [ 1 .. 255 ] characters ^[A-Za-z0-9-]+$ |
| parent_id | integer or null <int64> >= 1 |
Array of any or null (TagRequest) | |
| valid_from | string <date-time> |
| valid_to | string or null <date-time> |
{- "description": "Main warehouse location",
- "external_key": "wh1",
- "is_active": true,
- "name": "Warehouse 1",
- "parent_external_key": "wh1",
- "parent_id": 42,
- "tags": [
- {
- "tag_type": "rfid",
- "value": "string"
}
], - "valid_from": "2025-12-14T00:00:00Z",
- "valid_to": "2026-12-14T00:00:00Z"
}{- "data": {
- "created_at": "2025-04-29T12:34:56.000Z",
- "deleted_at": "2025-04-29T12:34:56.000Z",
- "description": "string",
- "external_key": "string",
- "id": 0,
- "is_active": true,
- "name": "string",
- "parent_external_key": "string",
- "parent_id": 0,
- "tags": [
- {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
], - "updated_at": "2025-04-29T12:34:56.000Z",
- "valid_from": "2025-04-29T12:34:56.000Z",
- "valid_to": "2025-04-29T12:34:56.000Z"
}
}Required scope: locations:write
Delete a location by its ID. Returns 204 on success, 404 if the location does not exist or has already been deleted, and 409 if the location has descendant locations or assets placed directly at it. Descendants must be reassigned or removed and placed assets must be moved or removed before their parent location can be deleted; bulk cascade is not supported.
| location_id required | integer <int64> [ 1 .. 2147483647 ] Location ID |
{- "error": {
- "detail": "string",
- "fields": [
- {
- "code": "required",
- "field": "string",
- "message": "string",
- "params": {
- "property1": null,
- "property2": null
}
}
], - "instance": "string",
- "request_id": "string",
- "status": 0,
- "title": "string",
- "type": "validation_error"
}
}Required scope: locations:read
Retrieve a location by its canonical ID. Returns 404 if not found.
Path-addressed retrieval bypasses the temporal-validity filter applied on list endpoints — any non-deleted location is returned regardless of its valid_from / valid_to values. Use this endpoint when you have an id and need the row even if its effective window has elapsed.
| location_id required | integer <int64> [ 1 .. 2147483647 ] Location ID |
{- "data": {
- "created_at": "2025-04-29T12:34:56.000Z",
- "deleted_at": "2025-04-29T12:34:56.000Z",
- "description": "string",
- "external_key": "string",
- "id": 0,
- "is_active": true,
- "name": "string",
- "parent_external_key": "string",
- "parent_id": 0,
- "tags": [
- {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
], - "updated_at": "2025-04-29T12:34:56.000Z",
- "valid_from": "2025-04-29T12:34:56.000Z",
- "valid_to": "2025-04-29T12:34:56.000Z"
}
}Required scope: locations:write
Apply a JSON Merge Patch (RFC 7396) to a location. Only fields included in the request body are changed; fields set to null clear the corresponding nullable column. Omitted fields are left unchanged. An empty body ({}) is a no-op and returns the current resource unchanged. Read-only fields are uniformly governed by the accept-if-matches, reject-if-differs rule: a value matching the current resource state is silently normalized out (so a verbatim GET → PATCH round-trip succeeds without manual scrubbing), and a differing value returns 400 with code: read_only. This applies to the server-managed surrogate id + timestamps (id, created_at, updated_at, deleted_at), the tags collection, and the external_key natural key. To re-parent, send EITHER parent_id (surrogate) OR parent_external_key (natural key); both forms accept null to clear the FK, and supplying both in the same body returns 400 ambiguous_fields (symmetric with CreateLocationRequest). Mutate external_key via POST /locations/{location_id}/rename; mutate tags via POST /locations/{location_id}/tags and DELETE /locations/{location_id}/tags/{tag_id}.
| location_id required | integer <int64> [ 1 .. 2147483647 ] Location ID |
Fields to merge-patch
| description | string or null [ 1 .. 1024 ] characters ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$ |
| is_active | boolean |
| name | string [ 1 .. 255 ] characters ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$ |
| parent_external_key | string or null [ 1 .. 255 ] characters ^[A-Za-z0-9-]+$ |
| parent_id | integer or null <int64> >= 1 |
| valid_from | string <date-time> |
| valid_to | string or null <date-time> |
{- "description": "Updated description",
- "is_active": true,
- "name": "Warehouse 1",
- "parent_external_key": "wh1",
- "parent_id": 42,
- "valid_from": "2025-12-14T00:00:00Z",
- "valid_to": "2026-12-14T00:00:00Z"
}{- "data": {
- "created_at": "2025-04-29T12:34:56.000Z",
- "deleted_at": "2025-04-29T12:34:56.000Z",
- "description": "string",
- "external_key": "string",
- "id": 0,
- "is_active": true,
- "name": "string",
- "parent_external_key": "string",
- "parent_id": 0,
- "tags": [
- {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
], - "updated_at": "2025-04-29T12:34:56.000Z",
- "valid_from": "2025-04-29T12:34:56.000Z",
- "valid_to": "2025-04-29T12:34:56.000Z"
}
}Required scope: locations:read
Sort order is fixed: ancestors are returned root first (walking up the parent_id chain), with id ascending as a deterministic tiebreaker. No sort query parameter is exposed because the natural order toward the root is the only meaningful order for this list.
| location_id required | integer <int64> [ 1 .. 2147483647 ] Location ID |
| limit | integer [ 1 .. 200 ] Default: 50 max 200 |
| offset | integer [ 0 .. 2147483647 ] Default: 0 min 0 |
{- "data": [
- {
- "created_at": "2025-04-29T12:34:56.000Z",
- "deleted_at": "2025-04-29T12:34:56.000Z",
- "description": "string",
- "external_key": "string",
- "id": 0,
- "is_active": true,
- "name": "string",
- "parent_external_key": "string",
- "parent_id": 0,
- "tags": [
- {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
], - "updated_at": "2025-04-29T12:34:56.000Z",
- "valid_from": "2025-04-29T12:34:56.000Z",
- "valid_to": "2025-04-29T12:34:56.000Z"
}
], - "limit": 50,
- "offset": 0,
- "total_count": 100
}Required scope: locations:read
Sort order is fixed: immediate children are returned ordered alphabetically by name ascending, with id ascending as a deterministic tiebreaker when sibling names collide. No sort query parameter is exposed because alphabetical-by-name is the only meaningful order for a single level of siblings.
| location_id required | integer <int64> [ 1 .. 2147483647 ] Location ID |
| limit | integer [ 1 .. 200 ] Default: 50 max 200 |
| offset | integer [ 0 .. 2147483647 ] Default: 0 min 0 |
{- "data": [
- {
- "created_at": "2025-04-29T12:34:56.000Z",
- "deleted_at": "2025-04-29T12:34:56.000Z",
- "description": "string",
- "external_key": "string",
- "id": 0,
- "is_active": true,
- "name": "string",
- "parent_external_key": "string",
- "parent_id": 0,
- "tags": [
- {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
], - "updated_at": "2025-04-29T12:34:56.000Z",
- "valid_from": "2025-04-29T12:34:56.000Z",
- "valid_to": "2025-04-29T12:34:56.000Z"
}
], - "limit": 50,
- "offset": 0,
- "total_count": 100
}Required scope: locations:read
Sort order is fixed: descendants are returned in depth-first tree order (each level sorted by lowercased external_key), with id ascending as a deterministic tiebreaker. No sort query parameter is exposed because the depth-first tree walk is the only meaningful order for this list.
| location_id required | integer <int64> [ 1 .. 2147483647 ] Location ID |
| limit | integer [ 1 .. 200 ] Default: 50 max 200 |
| offset | integer [ 0 .. 2147483647 ] Default: 0 min 0 |
{- "data": [
- {
- "created_at": "2025-04-29T12:34:56.000Z",
- "deleted_at": "2025-04-29T12:34:56.000Z",
- "description": "string",
- "external_key": "string",
- "id": 0,
- "is_active": true,
- "name": "string",
- "parent_external_key": "string",
- "parent_id": 0,
- "tags": [
- {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
], - "updated_at": "2025-04-29T12:34:56.000Z",
- "valid_from": "2025-04-29T12:34:56.000Z",
- "valid_to": "2025-04-29T12:34:56.000Z"
}
], - "limit": 50,
- "offset": 0,
- "total_count": 100
}Required scope: locations:write
Mutate the location's external_key. This operation is destructive to downstream joins because consumers of the natural key must re-resolve it. Only this row's external_key changes on the server; descendants are not modified.
The response includes descendant_count_affected (the live descendant count reachable through parent_id) so an integrator can decide whether to refresh derived natural-key joins for the subtree. external_key is immutable via PATCH; this operation is the only way to change it. Distinct from a regular PATCH in audit logs (different URL surface).
| location_id required | integer <int64> [ 1 .. 2147483647 ] Location ID |
New external_key
| external_key required | string [ 1 .. 255 ] characters ^[A-Za-z0-9-]+$ |
{- "external_key": "wh1"
}{- "data": {
- "created_at": "2025-04-29T12:34:56.000Z",
- "deleted_at": "2025-04-29T12:34:56.000Z",
- "description": "string",
- "external_key": "string",
- "id": 0,
- "is_active": true,
- "name": "string",
- "parent_external_key": "string",
- "parent_id": 0,
- "tags": [
- {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
], - "updated_at": "2025-04-29T12:34:56.000Z",
- "valid_from": "2025-04-29T12:34:56.000Z",
- "valid_to": "2025-04-29T12:34:56.000Z"
}, - "descendant_count_affected": 7
}Required scope: locations:write
| location_id required | integer <int64> [ 1 .. 2147483647 ] Location ID |
Tag to attach
| tag_type required | string Discriminator for the polymorphic Tag resource. |
| value required | string [ 1 .. 255 ] characters ^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$ |
{- "tag_type": "barcode",
- "value": "string"
}{- "data": {
- "id": 0,
- "tag_type": "rfid",
- "value": "string"
}
}Required scope: locations:write
Detach a tag from a location by its tag record id. First successful removal returns 204; repeated calls return 404 — consistent with top-level resource DELETE semantics. The cross-location / cross-org case (a tag that exists but is not attached to this location, or belongs to a different org) also surfaces as 404.
| location_id required | integer <int64> [ 1 .. 2147483647 ] Location ID |
| tag_id required | integer <int64> [ 1 .. 2147483647 ] Tag ID |
{- "error": {
- "detail": "string",
- "fields": [
- {
- "code": "required",
- "field": "string",
- "message": "string",
- "params": {
- "property1": null,
- "property2": null
}
}
], - "instance": "string",
- "request_id": "string",
- "status": 0,
- "title": "string",
- "type": "validation_error"
}
}Returns the organization scoped by the API key presented in Authorization. Intended as a lightweight health-check primitive for integrators verifying a key works end-to-end.
{- "data": {
- "api_key_id": "550e8400-e29b-41d4-a716-446655440000",
- "id": 42,
- "name": "Acme Logistics",
- "scopes": [
- "assets:read",
- "assets:write"
]
}
}Required scope: tracking:read
Snapshot of each asset's most recent location, filterable by either side of the join. Filter by location (location_id / location_external_key) to retrieve everything currently at a place; filter by asset (asset_id / asset_external_key, repeatable) to resolve a batch of assets from a master system to their current locations in one round-trip. Within each pair the surrogate and natural-key forms are mutually exclusive (400 ambiguous_fields if both are supplied); the asset and location filter pairs are independent and intersect when combined. Because this view is derived from immutable scan history, it can resolve references for assets that have since been deleted. By default those rows are excluded; pass include_deleted=true to include them, and check asset_deleted_at to distinguish deleted from live.
Temporal validity is applied to both joined entities. Assets whose effective window is past or future are excluded entirely. Locations whose effective window is past or future surface with null location_id / location_external_key while the parent asset row remains visible. Soft-deleted locations are projected the same way here — null on the report row — even though the identifier still lives on the location row; reports endpoints intentionally hide tombstoned anchor points from scan-derived summaries. Use the locations endpoint with include_deleted=true to retrieve the underlying identifier.
| limit | integer [ 1 .. 200 ] Default: 50 max 200 |
| offset | integer [ 0 .. 2147483647 ] Default: 0 min 0 |
| location_id | Array of integers <int64> [ items <int64 > [ 1 .. 2147483647 ] ] Default: "" filter by location id (canonical, may repeat); mutually exclusive with location_external_key (400 ambiguous_fields if both supplied) |
| location_external_key | Array of strings[ items^[A-Za-z0-9-]+$ ] Default: "" filter by location external_key (may repeat); mutually exclusive with location_id (400 ambiguous_fields if both supplied) |
| asset_id | Array of integers <int64> [ items <int64 > [ 1 .. 2147483647 ] ] Default: "" filter by asset id (canonical, may repeat); mutually exclusive with asset_external_key (400 ambiguous_fields if both supplied) |
| asset_external_key | Array of strings[ items^[A-Za-z0-9-]+$ ] Default: "" filter by asset external_key (may repeat); mutually exclusive with asset_id (400 ambiguous_fields if both supplied) |
| q | string^[^\x00-\x08\x0B\x0C\x0E-\x1F\x7F]*$ substring search (case-insensitive) on asset name, external_key, and active tag values |
| include_deleted | boolean Default: false include rows for soft-deleted assets |
| sort | string^(|(?:asset_last_seen|-asset_last_seen|asset_... Default: "" comma-separated sort fields; prefix '-' for DESC |
{- "data": [
- {
- "asset_deleted_at": "2025-04-29T12:34:56.000Z",
- "asset_external_key": "string",
- "asset_id": 0,
- "asset_last_seen": "2025-04-29T12:34:56.000Z",
- "location_external_key": "string",
- "location_id": 0
}
], - "limit": 50,
- "offset": 0,
- "total_count": 100
}