Spatial Domain
Overview
The Spatial Domain dictates the physical deployment hierarchy of the platform. It maps out geographical locations, venues, establishments, zones, and endpoints — the physical walls/mounts where devices are attached. Spaces defined here are referenced by the Catalogue domain when assigning Media Channels to physical locations, but the Spatial domain owns and manages all location data independently.
Architecture
┌──────────────────────────────────────────────────────────────────┐
│ Spatial Domain │
│ │
│ Venue (root) │
│ └── Building │
│ └── Floor │
│ └── Space ──── Endpoint ──── Device │
│ │ (physical mount) (hardware) │
│ │ │
│ Zone (logical grouping of spaces) │
│ │
│ Venue ──── Establishment (tenant/operator inside a venue) │
│ via venue_relationships │
│ │
│ Location ──── Workspace (via workspace_locations) │
│ │
│ [ Catalogue domain references Space ID via │
│ channel_space_assignments — read-only cross-domain link ] │
└──────────────────────────────────────────────────────────────────┘
Request Headers used across all endpoints:
| Header | Description |
|---|---|
X-Organization-ID |
Required on most endpoints for tenant scoping |
X-Workspace-ID |
Optional — filters location trees and device counts to a specific workspace |
userId (via c.Locals) |
Required on create/assign operations |
Data Model
Location
The core entity. Represents any node in the physical hierarchy.
| Field | Type | Notes |
|---|---|---|
id |
string | UUID |
organizationId |
string | Tenant scope |
parentId |
*string | Parent location in hierarchy |
locationType |
LocationType |
venue / building / floor / space |
name |
string | Display name |
shortName / code |
string | Optional identifiers |
status |
string | Default Active |
operatingHours |
OperatingHours |
JSON map by day of week |
venueRole |
string | property or establishment (for venue type only) |
venueCategory |
string | e.g. mall, airport |
spaceType / placement |
string | For space type only |
isDOOHEnabled |
bool | Whether space supports DOOH advertising |
isDoohEnabled, avgDailyFootfall |
DOOH-specific fields | |
latitude / longitude |
float64 | Geo coordinates |
Hierarchy rules (enforced on create):
- venue → root-level only (no parentId), unless venueRole = establishment which requires parentVenueId
- building → parent must be venue
- floor → parent must be building
- space → parent must be floor
LocationTree / LocationGroupTree
Recursive tree response returned by /tree and /group-tree. Each node includes:
- Aggregated deviceCount, onlineCount, offlineCount (rolled up from all children)
- zones for space nodes (from zone_spaces join)
- Full metadata fields in group-tree variant
Endpoint
A physical mount point inside a space where a device is installed.
| Field | Type | Notes |
|---|---|---|
spaceId |
string | Must be a space-type location |
deviceId |
*string | Linked device (nullable = vacant) |
status |
string | Vacant (default) or Occupied |
isMonetized |
bool | Whether endpoint is monetized for DOOH |
Zone
A logical grouping that aggregates multiple spaces, typically across one floor.
| Field | Type | Notes |
|---|---|---|
floorId |
*string | Must reference a floor location |
zoneCode |
string | Short identifier |
type |
string | Zone category |
status |
string | Active by default |
Spaces are linked to zones via zone_spaces join table.
Establishment
A tenant or operator that occupies space within a venue (legacy model, separate from location hierarchy).
| Field | Type | Notes |
|---|---|---|
venueId |
string | Parent venue |
contractRef |
string | Contract reference number |
type |
string | Establishment category |
status |
string | Default Active |
VenueType
A reference/lookup table for categorizing venue types (e.g. Mall, Hospital). System-wide, not org-scoped.
VenueRelationship
Links a property venue to an establishment venue with commercial terms.
| Field | Type | Notes |
|---|---|---|
propertyId |
string | Parent property venue |
establishmentId |
string | Child establishment venue |
commercialModel |
string | e.g. revenue-share, lease |
revenueSharePct |
*float64 | Revenue share percentage |
leaseStart / leaseEnd |
*string | Contract date range |
Tags
Labels that can be assigned to locations for filtering/categorization.
| Field | Type | Notes |
|---|---|---|
name |
string | Tag label |
colorCode |
string | Hex color for UI display |
category |
string | Tag grouping |
Linked to locations via location_tags join table (full replace on assign).
Routes
Locations — /locations
| Method | Path | Handler | Description |
|---|---|---|---|
| GET | /locations |
HandleGetLocations |
Flat list, filterable by parent_id, location_type |
| GET | /locations/tree |
HandleGetLocationTree |
Recursive tree (id, name, type, device counts, zones) |
| GET | /locations/group-tree |
HandleGetLocationGroupTree |
Full metadata recursive tree, workspace-aware |
| GET | /locations/spaces |
HandleGetSpaces |
All spaces with endpoint/device stats and parent breadcrumb |
| POST | /locations |
HandleCreateLocation |
Create location (enforces hierarchy rules) |
| PUT | /locations/:locationId |
HandleUpdateLocation |
Update location metadata |
| DELETE | /locations/:locationId |
HandleDeleteLocation |
Delete location |
| GET | /locations/:locationId/devices |
HandleGetLocationDevices |
Devices under location (recursive via CTE), workspace-aware |
| GET | /locations/:locationId/endpoints |
HandleGetEndpoints |
Endpoints in a space with device status |
| POST | /locations/:locationId/endpoints |
HandleCreateEndpoint |
Create endpoint (space only) |
| GET | /locations/:locationId/tags |
HandleGetLocationTags |
Tags assigned to location |
| POST | /locations/:locationId/tags |
HandleAssignTagsToLocation |
Replace all tags (transactional full replace) |
| POST | /locations/:locationId/assign/:workspaceId |
HandleAssignLocationToWorkspace |
Assign location to workspace |
| DELETE | /locations/:locationId/assign/:workspaceId |
HandleUnassignLocationFromWorkspace |
Remove workspace assignment |
Endpoints — /endpoints
| Method | Path | Handler | Description |
|---|---|---|---|
| PUT | /endpoints/:endpointId/assign-device |
HandleAssignDeviceToEndpoint |
Assign device to endpoint; auto-vacates previous endpoint and updates devices.location_id |
Establishments — /establishments
| Method | Path | Handler | Description |
|---|---|---|---|
| PUT | /establishments/:establishmentId |
HandleUpdateEstablishment |
Update establishment |
| DELETE | /establishments/:establishmentId |
HandleDeleteEstablishment |
Unlinks spaces, then deletes |
Venue Types — /venue-types
| Method | Path | Handler | Description |
|---|---|---|---|
| GET | /venue-types |
HandleGetVenueTypes |
List active venue types (system-wide) |
| POST | /venue-types |
HandleCreateVenueType |
Create venue type (unique name enforced) |
Venues — /venues
| Method | Path | Handler | Description |
|---|---|---|---|
| GET | /venues/:venueId/establishments |
HandleGetEstablishment |
List establishments under a venue |
| POST | /venues/:venueId/establishments |
HandleCreateEstablishment |
Create establishment under a venue |
Zones — /zones
| Method | Path | Handler | Description |
|---|---|---|---|
| GET | /zones |
HandleGetZone |
List zones, filterable by floor_id |
| POST | /zones |
HandleCreateZone |
Create zone (validates floorId type) |
| DELETE | /zones/:zoneID |
HandleDeleteZone |
Delete zone |
Note: Zone–space assignment routes (
/zones/:zoneId/spaces) exist in the handler (HandleGetZoneSpaces,HandleAssignSpaceToZone,HandleRemoveSpaceFromZone) but are not yet registered inroutes.go.
Key Creation Flows
1. Create the Location Hierarchy
# 1. Create a Venue (root)
POST /locations
Body: { "name": "Central Mall", "locationType": "venue", "venueRole": "property",
"venueCategory": "mall", "city": "Bangkok", "country": "TH", "status": "Active" }
→ Returns: { "id": "venue-uuid" }
# 2. Create a Building under the Venue
POST /locations
Body: { "name": "Main Building", "locationType": "building", "parentId": "venue-uuid" }
→ Returns: { "id": "building-uuid" }
# 3. Create a Floor under the Building
POST /locations
Body: { "name": "Floor 1", "locationType": "floor", "parentId": "building-uuid" }
→ Returns: { "id": "floor-uuid" }
# 4. Create a Space under the Floor
POST /locations
Body: { "name": "Lobby Screen Area", "locationType": "space", "parentId": "floor-uuid",
"spaceType": "lobby", "maxCapacity": 500 }
→ Returns: { "id": "space-uuid" }
2. Create an Endpoint and Assign a Device
# 1. Create an Endpoint in the space
POST /locations/:spaceId/endpoints
Body: { "name": "Screen Mount A", "code": "EP-001", "isMonetized": true }
→ Validates: locationId must be type 'space'
→ Status defaults to 'Vacant'
→ Returns: { "id": "endpoint-uuid" }
# 2. Assign a provisioned Device to the Endpoint
PUT /endpoints/:endpointId/assign-device
Headers: X-Organization-ID
Body: { "deviceId": "device-uuid" }
→ Auto-vacates previous endpoint for this device (if any)
→ Sets endpoint status to 'Occupied'
→ Updates devices.location_id = spaceId
→ Returns: { "message": "Device assigned to endpoint" }
3. Create an Establishment Venue (Tenant)
# Option A: Via /locations (creates venue_relationship automatically)
POST /locations
Body: {
"name": "Coffee Shop Co.",
"locationType": "venue",
"venueRole": "establishment",
"engagementRole": "tenant",
"parentVenueId": "property-venue-uuid"
}
→ Creates location with venueRole = establishment
→ Auto-inserts venue_relationships record (property → establishment)
→ Returns: { "id": "establishment-venue-uuid" }
# Option B: Via /venues/:venueId/establishments (legacy)
POST /venues/:venueId/establishments
Body: { "name": "Coffee Shop Co.", "type": "F&B", "contractRef": "CTR-001" }
→ Returns full establishment object
4. Assign Location to a Workspace
POST /locations/:locationId/assign/:workspaceId
→ Validates: location belongs to org, workspace belongs to org
→ Inserts workspace_locations (ON CONFLICT DO NOTHING)
→ Returns: { "message": "Location assigned to workspace" }
Note: When creating a location while
X-Workspace-IDheader is present, the new location is automatically assigned to that workspace.
Changelog
| Date | Author | Change |
|---|---|---|
| 2026-06-17 | — | Added description, architecture, data model, creation flows |
| 2026-06-16 | Pin | Created the openapi |