Introduction
Welcome to the API documentation for the Sustainder Brokerage Layer (SBL).
The base URL for the V2 API is https://httpapi.sustainder.com/v2.
This documentation covers all available API endpoints including:
- V2 API — The primary REST API for managing lighting infrastructure, devices, groups, dimming schemes, and more.
- Admin API — Administrative endpoints for bulk operations and gateway management.
- TALQ API — TALQ Smart City protocol integration for intelligent outdoor lighting.
- TALQ CMS API — Inbound gateway registration endpoints following the TALQ CMS specification.
- Vendor APIs — Third-party integrations (Flitsmeister, Intemo).
Note: The deprecated Regulus and legacy Devices API documentation is available here.
Concepts
Before continuing, let's introduce some important concepts in the API.
Media Type
An example HAL+JSON response:
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/lcms"
},
"direct_control": {
"href": "https://httpapi.sustainder.com/v2/lcms/functions/direct-control"
}
},
"_embedded": {
"lcms": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
}
},
"lcm_id": "LCM-001234",
"status": "ONLINE"
}
]
},
"_total": 42
}
All communication with the API uses JSON. Specifically, we support the standard media type HAL+JSON. This standard specifies a format for links between resources as well as how resources can be embedded.
Every response includes:
_links— Navigation links to related resources, always including aselflink._embedded— Nested/related resources included inline to reduce the number of API calls._total— The total count of items in collection responses, ornullfor single-resource responses.
There are various libraries available to help integration with HAL+JSON, however none is required; all responses are valid JSON.
Dates and Times
All dates and times provided by the API are in UTC following the ISO 8601 standard:
2024-11-15T14:32:08Z
When submitting dates in requests, always use UTC.
Domain Entities
Here we introduce the entities in the domain of our API. Understanding these terms will help greatly in integrating with the API.
| Entity | Description |
|---|---|
| Application | A customer project/system containing devices, gateways, and configurations. Users can have access to one or more applications. |
| Node | A physical entity in the network. It has its own identity and location, and may host one or more services. |
| Gateway | A service provided by a node which provides internet connectivity for other nodes via RF (radio frequency). |
| LCM | Lighting Control Module — the controller inside a luminaire that manages dimming, energy metering, and status reporting. |
| Sensor | A service that a node can provide, which supplies measurement data (e.g., ambient light, temperature, motion, tilt). |
| Group | A logical grouping of nodes for batch operations like direct control or dimming scheme assignment. |
| Setting | A persistent configuration for a node service (e.g., default light levels). Settings remain until explicitly changed. |
| Function | A short-lived operation performed on a node service (e.g., direct control override, status request). |
| Dimming Scheme | A time-based schedule that defines light levels throughout the day/night cycle. |
| Calendar | A weekly schedule that maps dimming schemes to specific days of the week. |
Pagination
Collection endpoints support pagination via query parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page |
integer | 1 | The page number to retrieve (1-indexed). |
pageSize |
integer | varies | The number of items per page. |
The response includes _total indicating the total number of items across all pages.
Rate Limiting
Certain endpoints are rate-limited to protect system stability. When rate limits are exceeded, the API returns HTTP 429 Too Many Requests. Implement exponential backoff in your client when encountering this response.
Application Context
Most API endpoints require an application_artkey query parameter or request body field. This scopes the request to a specific application/system. You can retrieve your available applications from the /auth/me endpoint.
Getting Started
Authentication
Authentication is done using JSON Web Tokens (JWT) (recommended) or Basic Access Authentication over HTTPS. Both methods require a username and password provided by Sustainder.
JWT Authentication (Recommended)
Step 1: Retrieve a JWT token
curl -X POST "https://httpapi.sustainder.com/v2/auth/jwt" \
-H "Content-Type: application/json" \
-d '{"username": "john.doe@example.com", "password": "s3cur3P@ssw0rd"}'
Response:
{
"name": "john.doe@example.com",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NTY3ODkwfQ.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U",
"auth-header": "JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NTY3ODkwfQ.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"
}
Step 2: Use the token in subsequent requests
curl "https://httpapi.sustainder.com/v2/welcome" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
JWT allows you to generate a token for a limited period without storing and sending the password for every request. This is the preferred method for production use.
The auth-header field in the response provides the complete value for the Authorization header:
Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
In the following sections, authentication headers will be omitted for brevity but are always required.
Basic Access Authentication
Basic Auth example:
curl "https://httpapi.sustainder.com/v2/welcome" \
-u "john.doe@example.com:s3cur3P@ssw0rd"
Basic Auth sends both username and password with every request. This method is simpler but less secure — use JWT for production environments.
Making Your First Request
Retrieve your user information and available applications
curl "https://httpapi.sustainder.com/v2/auth/me" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response:
{
"name": "john.doe@example.com",
"applications": [
{
"artkey": 35,
"name": "Amsterdam Centrum",
"image": null,
"is_tilted_warning_threshold": 10.0,
"is_tilted_error_threshold": 45.0,
"permission_asset_management": true,
"permission_tilted": true
}
]
}
After authenticating, call /v2/auth/me to discover your available applications. Most endpoints require an application_artkey to scope results to a specific application.
Browsing Your Infrastructure
List all nodes
curl "https://httpapi.sustainder.com/v2/nodes" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Get details for a specific node
curl "https://httpapi.sustainder.com/v2/nodes/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Use the Nodes endpoint to see all physical entities (poles/locations) in your system. Each node provides one or more services:
- LCM (Lighting Control Module) — light control and energy metering.
- Gateway — internet connectivity for other nodes.
- Sensors — environmental data (ambient light, temperature, motion, tilt).
Node links show available services
{
"_links": {
"self": { "href": "/v2/nodes/a1b2c3d4-e5f6-7890-abcd-ef1234567890" },
"lcm_service": { "href": "/v2/lcms/LCM-001234" },
"sensors_service": { "href": "/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890" },
"gateway_service": { "href": "/v2/gateways/GW-005678" }
}
}
Follow the _links to navigate to the specific services a node provides.
Controlling Lights
Set a direct control override
curl -X POST "https://httpapi.sustainder.com/v2/lcms/functions/direct-control" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"override": [
{
"lcm_id": "LCM-001234",
"override": [80, 0, 0, 0],
"duration": 30
}
],
"application_artkey": 35
}'
The API provides several ways to control lighting:
| Action | Endpoint | Description |
|---|---|---|
| Direct Control | /lcms/functions/direct-control |
Temporarily override LCM light levels |
| Clear Override | /lcms/functions/clear-override |
Return LCM to its dimming scheme |
| Light Levels | /lcms/settings/light-levels |
Set maximum output levels |
| Dimming Schemes | /lcms/settings/dimming-schemes |
Assign time-based schedules |
| Preview | /lcms/functions/preview-light-levels |
Preview what new max levels would look like |
The same operations are available at group level via the Groups endpoints.
Queuing Settings for Offline Devices
Set light levels with offline queuing
curl -X POST "https://httpapi.sustainder.com/v2/lcms/settings/light-levels" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"light_levels": [
{
"lcm_id": "LCM-001234",
"light_levels": [100, 100, 100, 100]
}
],
"queue_when_offline": true,
"application_artkey": 35
}'
Settings endpoints support the queue_when_offline flag. When set to true, if a device is offline, the setting is queued and applied when the device next comes online. When false (default), the command is discarded if the device is offline.
If multiple settings are queued for the same device, only the most recent setting is applied.
Sensor Data
Get all sensors for a node
curl "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Get a specific sensor reading
curl "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890/ambient" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Sensors provide the latest measurement data for a node. The API returns the most recent value, timestamp, and unit for each sensor. Available sensor types include power, temperature, ambient light, tilt, and more.
Webhooks
List available webhook types
curl "https://httpapi.sustainder.com/v2/webhooks" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Subscribe to a webhook
curl -X POST "https://httpapi.sustainder.com/v2/webhooks/dimming_scheme" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"remote_url": "https://mycompany.com/sustainder/dimming_scheme",
"auth_method": "BASIC",
"username": "webhook-user",
"password_or_token": "webhook-secret",
"application_artkey": 35
}'
Nodes communicate asynchronously with the SBL. Webhooks let you subscribe to events (status updates, sensor data, dimming scheme changes, etc.) so your system receives real-time notifications. See the Webhooks section for details.
Error Handling
Example error response
{
"title": "Validation Error",
"message": "The field 'lcm_id' is required.",
"logref": "err-20241115-143208-abc123"
}
The API uses standard HTTP status codes:
| Status | Meaning |
|---|---|
| 200 | Success |
| 201 | Resource created |
| 202 | Accepted (async processing) |
| 204 | No content (successful deletion) |
| 207 | Multi-status (batch operations with mixed results) |
| 400 | Bad request / validation error |
| 401 | Unauthorized — invalid or missing token |
| 403 | Forbidden — insufficient permissions |
| 404 | Resource not found |
| 422 | Unprocessable entity — business logic error |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
| 504 | Gateway timeout — device is offline |
Error responses follow the vnd.error+json format with a title, message, and optional logref for support reference.
Resources Overview
The following is a complete overview of all available resources in the V2 API. All endpoints are prefixed with /v2.
Authentication
| Method | Endpoint | Description |
|---|---|---|
| POST | /auth/jwt | Request a JWT token |
| POST | /auth/jwt/refresh | Refresh an existing JWT token |
| POST | /auth/jwt/otp | Request a JWT token via one-time password |
| POST | /auth/reset-password | Request a password reset email |
| POST | /auth/change-password | Change the current user's password |
| POST | /auth/logout | Logout and blacklist the current token |
| GET | /auth/me | Get current user info and applications |
Nodes
| Method | Endpoint | Description |
|---|---|---|
| GET | /nodes | List all nodes |
| GET | /nodes/{node_id} | Get node details |
| PATCH | /nodes/{node_id} | Update node data |
LCMs (Lighting Control Modules)
| Method | Endpoint | Description |
|---|---|---|
| GET | /lcms | List all LCMs |
| GET | /lcms/{lcm_id} | Get LCM details |
| GET | /lcms/{lcm_id}/status | Request LCM status update |
| GET | /lcms/{lcm_id}/sensor_status | Request LCM sensor status |
| GET | /lcms/{lcm_id}/zhaga-status | Request Zhaga diagnostics |
| POST | /lcms/functions/clear-override | Clear direct control override |
| POST | /lcms/functions/direct-control | Set direct control override |
| POST | /lcms/functions/preview-light-levels | Preview light levels |
| POST | /lcms/settings/light-levels | Set maximum light levels |
| POST | /lcms/settings/dimming-schemes | Assign dimming schemes |
| GET | /lcms/product-passport | Get LCM product passport |
Groups
| Method | Endpoint | Description |
|---|---|---|
| GET | /groups | List all groups |
| GET | /groups/{group_id} | Get group details |
| GET | /groups/migrations | Get group migration status |
| GET | /groups/{group_id}/devices/{device_id} | Get device within a group |
| POST | /groups/functions/direct-control | Direct control for a group |
| POST | /groups/functions/direct-control-all | Direct control for all groups |
| POST | /groups/functions/clear-override | Clear override for a group |
| POST | /groups/functions/clear-override-all | Clear override for all groups |
| POST | /groups/settings/light-levels | Set light levels for a group |
Gateways
| Method | Endpoint | Description |
|---|---|---|
| GET | /gateways | List all gateways |
| GET | /gateways/{gateway_id} | Get gateway details |
| GET | /gateways/{gateway_id}/sunsetsunrise | Get sunset/sunrise times |
Sensors
| Method | Endpoint | Description |
|---|---|---|
| GET | /sensors/{node_id} | List sensors for a node |
| GET | /sensors/{node_id}/{sensor_name} | Get specific sensor details |
Dimming Schemes
| Method | Endpoint | Description |
|---|---|---|
| GET | /dimming-schemes | List all dimming schemes |
| GET | /dimming-schemes-application | List application dimming schemes |
| GET | /dimming-schemes/{dimming_scheme_id} | Get dimming scheme details |
| GET | /dimming-scheme-exceptions | List dimming scheme exceptions |
| GET | /dimming-scheme-exceptions/{exception_artkey} | Get exception details |
| GET | /dimming-scheme-schedules | List dimming schedules |
| GET | /dimming-scheme-schedules/{schedule_artkey} | Get schedule details |
| POST | /dimming-scheme-schedules/{schedule_artkey}/apply | Apply a schedule |
Motion Configuration
| Method | Endpoint | Description |
|---|---|---|
| GET | /motion-configs | List motion configurations |
| GET | /motion-configs/{motion_config_id} | Get motion config details |
| GET | /motion-configs/{motion_config_id}/device-triggers | Get device triggers |
| GET | /motion-configs/{motion_config_id}/road-sections | Get road sections |
| GET | /motion-configs/{motion_config_id}/devices | Get config devices |
| GET | /motion-configs/{motion_config_id}/road-sections/{road_section_id} | Get road section devices |
| POST | /motion-configs/{id}/road-sections/{id}/invert-devices | Invert road section devices |
| GET | /motion-configs/gateways | Get application gateways for motion |
| GET | /motion-configs/devices | Get application devices for motion |
| GET | /motion-configs/data | Get motion data |
| GET | /motion-data/heatmap | Get motion heatmap |
| GET | /motion-data/heatmap/devices | Get device motion intensity |
Layers
| Method | Endpoint | Description |
|---|---|---|
| GET | /layers | List layers |
| POST | /layers | Create a layer |
| GET | /layers/{layer_id} | Get layer details |
| POST | /layers/{layer_id}/status | Enable/disable a layer |
| POST | /layers/validate | Validate layer configuration |
Custom Fields
| Method | Endpoint | Description |
|---|---|---|
| GET | /custom-fields | List custom field definitions |
| POST | /custom-fields | Create a custom field |
| GET | /custom-fields/{field_id} | Get custom field details |
| PATCH | /custom-fields/{field_id} | Update a custom field |
| DELETE | /custom-fields/{field_id} | Delete a custom field |
Buildings
| Method | Endpoint | Description |
|---|---|---|
| GET | /buildings/locations | Get building locations |
| GET | /buildings/{building_artkey} | Get building details |
| GET | /buildings/{id}/floors/{id} | Get floor details |
| GET | /buildings/{id}/floors/{id}/controllers | Get floor controllers |
| GET | /buildings/{id}/floors/{id}/spaces/{id} | Get space details |
| GET | /buildings/{id}/floors/{id}/lamps/channels | Get lamp channels |
| GET | /buildings/{id}/floors/{id}/triggers/{id} | Get trigger details |
| GET | /buildings/{id}/floors/{id}/triggers/{id}/configuration | Get trigger config |
Cameras
| Method | Endpoint | Description |
|---|---|---|
| GET | /cameras | List cameras |
| GET | /cameras/{camera_id} | Get camera details |
| GET | /lcms/{lcm_id}/camera | Get camera controller info |
Errors & Issues
| Method | Endpoint | Description |
|---|---|---|
| GET | /issues | List issues (errors) |
| GET | /errors | List errors (alias of /issues) |
| GET | /device-errors | List all device errors |
| GET | /device-errors/{device_id} | Get errors for a specific device |
| GET | /alarm-logbook | Get alarm logbook entries |
| GET | /types/error | Get all error types |
Device Notes & Comments
| Method | Endpoint | Description |
|---|---|---|
| GET | /lcms/{lcm_id}/notes | List device notes |
| POST | /lcms/{lcm_id}/notes | Create a device note |
| GET | /lcms/{lcm_id}/notes/{note_id} | Get a note |
| PUT | /lcms/{lcm_id}/notes/{note_id} | Update a note |
| DELETE | /lcms/{lcm_id}/notes/{note_id} | Delete a note |
| GET | /lcms/{lcm_id}/comments | List device comments |
| POST | /lcms/{lcm_id}/comments | Create a comment |
| GET | /lcms/{lcm_id}/comments/{comment_id} | Get a comment |
| PUT | /lcms/{lcm_id}/comments/{comment_id} | Update a comment |
| DELETE | /lcms/{lcm_id}/comments/{comment_id} | Delete a comment |
Device Logbook
| Method | Endpoint | Description |
|---|---|---|
| GET | /lcms/{lcm_id}/logbook | Get device settings history |
| GET | /lcms/{lcm_id}/logbook/comments | Get logbook comments |
| POST | /lcms/{lcm_id}/logbook/comments | Add a logbook comment |
Dummy Devices
| Method | Endpoint | Description |
|---|---|---|
| POST | /dummy-devices/import | Import dummy devices from Excel |
| GET | /dummy-devices/locations | Get dummy device locations |
| GET | /dummy-devices/template | Download import template |
| GET | /dummy-devices/{device_id} | Get dummy device details |
| PUT | /dummy-devices/{device_id} | Update a dummy device |
| DELETE | /dummy-devices/{device_id} | Delete a dummy device |
Node Replacements
| Method | Endpoint | Description |
|---|---|---|
| GET | /node-replacement-jobs | List replacement jobs |
| POST | /node-replacement-jobs | Create a replacement job |
| POST | /node-replacement-jobs/{job_id}/confirm | Confirm a replacement |
Import & Export
| Method | Endpoint | Description |
|---|---|---|
| POST | /system-export | Initiate a system export |
| GET | /systems-export/files | List export files |
| GET | /systems-export/files/{file_id} | Download an export file |
| POST | /system-import | Import devices |
| POST | /system-import/validate | Validate import fields |
Users
| Method | Endpoint | Description |
|---|---|---|
| GET | /users | List users |
| GET | /users/{user_artkey} | Get user details |
Preferences & Settings
| Method | Endpoint | Description |
|---|---|---|
| GET | /email-preference | Get email preferences |
| POST | /email-preference | Update email preferences |
| GET | /tos | Get terms of service |
| POST | /tos | Accept terms of service |
| POST | /tos/skip | Skip terms of service |
Dashboard
| Method | Endpoint | Description |
|---|---|---|
| GET | /dashboard/alarms | Get dashboard alarms |
| GET | /dashboard/updates | Get dashboard updates |
| GET | /dashboard/devices | Get dashboard device summary |
Application
| Method | Endpoint | Description |
|---|---|---|
| GET | /app/icon | Get application icon |
| GET | /app/heatmap-intensity | Get heatmap intensity config |
Options
| Method | Endpoint | Description |
|---|---|---|
| GET | /options/optics | Get available optics options |
| GET | /options/ccts | Get available CCT options |
Energy Data
| Method | Endpoint | Description |
|---|---|---|
| GET | /energy-data | Get energy data |
| GET | /energy-stats | Get energy statistics |
| GET | /recent-energy-stats | Get recent energy stats |
| GET | /lcms/{lcm_id}/energy-data | Get energy data for a device |
Miscellaneous
| Method | Endpoint | Description |
|---|---|---|
| GET | /welcome | Welcome / test endpoint |
| GET | /coordinate-systems | Get coordinate systems |
| GET | /feature-flags | Get feature flags |
| GET | /releases | List releases |
| POST | /releases | Create a release |
| POST | /releases/read | Mark releases as read |
| GET | /faq | Get FAQ |
| POST | /feedbacks/malfunction | Submit malfunction feedback |
| GET | /jira-tickets | Get Jira tickets for a device |
| GET | /lcms/product-passport | Get LCM product passport |
Webhooks
| Method | Endpoint | Description |
|---|---|---|
| GET | /webhooks | List available webhook types |
| GET/POST | /webhooks/{event_type} | Manage webhook for event type |
Geolocation
| Method | Endpoint | Description |
|---|---|---|
| GET | /geo/coordinates | Lookup coordinates from address |
| GET | /geo/addresses | Reverse geocode (coordinates to address) |
System
| Method | Endpoint | Description |
|---|---|---|
| GET | / | API root endpoint |
| GET | /status | Health check |
| GET | /version | API version |
| GET | /ping | Ping |
Root [GET]
Request
curl "https://httpapi.sustainder.com/v2/" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/"
},
"jwt": {
"href": "https://httpapi.sustainder.com/v2/auth/jwt"
},
"me": {
"href": "https://httpapi.sustainder.com/v2/auth/me"
},
"welcome": {
"href": "https://httpapi.sustainder.com/v2/welcome"
},
"nodes": {
"href": "https://httpapi.sustainder.com/v2/nodes"
},
"lcms": {
"href": "https://httpapi.sustainder.com/v2/lcms"
},
"gateways": {
"href": "https://httpapi.sustainder.com/v2/gateways"
},
"groups": {
"href": "https://httpapi.sustainder.com/v2/groups"
},
"issues": {
"href": "https://httpapi.sustainder.com/v2/issues"
},
"webhooks": {
"href": "https://httpapi.sustainder.com/v2/webhooks"
}
},
"_total": null,
"message": "Welcome to the Sustainder Brokerage Layer V2 API."
}
The root resource /v2/ is the entrypoint of the API. It provides HAL links for navigating to all major resource collections.
Auth
This section covers all authentication and user identity endpoints.
/auth/jwt [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/auth/jwt" \
-H "Content-Type: application/json" \
-d '{
"username": "john.doe@example.com",
"password": "s3cur3P@ssw0rd"
}'
Request body
{
"username": "john.doe@example.com",
"password": "s3cur3P@ssw0rd"
}
Response (200 OK)
{
"name": "john.doe@example.com",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NTY3ODkwLCJleHAiOjE3MzE2ODI3Mjh9.KpZx7Gf3vOYKmST8Q_dozjgNryP4J3jVmNHl0w5N_Xg",
"auth-header": "JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NTY3ODkwLCJleHAiOjE3MzE2ODI3Mjh9.KpZx7Gf3vOYKmST8Q_dozjgNryP4J3jVmNHl0w5N_Xg"
}
Authenticates with username and password to obtain a JWT token. The auth-header field contains the complete value for the HTTP Authorization header.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| username | string | Yes | The user's email address or username. |
| password | string | Yes | The user's password. |
Response Fields
| Field | Type | Description |
|---|---|---|
| name | string | The authenticated username. |
| token | string | The JWT token value. |
| auth-header | string | The complete Authorization header value (JWT <token>). |
Error Responses
| Status | Description |
|---|---|
| 401 | Invalid credentials. |
/auth/jwt/refresh [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/auth/jwt/refresh" \
-H "Content-Type: application/json" \
-d '{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NTY3ODkwLCJleHAiOjE3MzE2ODI3Mjh9.KpZx7Gf3vOYKmST8Q_dozjgNryP4J3jVmNHl0w5N_Xg"
}'
Request body
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NTY3ODkwLCJleHAiOjE3MzE2ODI3Mjh9.KpZx7Gf3vOYKmST8Q_dozjgNryP4J3jVmNHl0w5N_Xg"
}
Response (200 OK)
{
"name": "john.doe@example.com",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NTY3ODkwLCJleHAiOjE3MzE2ODk5Mjh9.NewRefreshedTokenValueHere123456",
"auth-header": "JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NTY3ODkwLCJleHAiOjE3MzE2ODk5Mjh9.NewRefreshedTokenValueHere123456"
}
Refreshes a valid (but possibly near-expiry) JWT token. Returns a new token with an extended expiration time.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| token | string | Yes | The current JWT token to refresh. |
Response Fields
Same as /auth/jwt.
Error Responses
| Status | Description |
|---|---|
| 401 | Token is invalid or has already expired beyond the refresh window. |
/auth/jwt/otp [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/auth/jwt/otp" \
-H "Content-Type: application/json" \
-d '{
"otp": "a7b3c9d1-e2f4-4a5b-8c6d-7e8f9a0b1c2d"
}'
Request body
{
"otp": "a7b3c9d1-e2f4-4a5b-8c6d-7e8f9a0b1c2d"
}
Response (200 OK)
{
"name": "john.doe@example.com",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NTY3ODkwLCJleHAiOjE3MzE2ODI3Mjh9.OTPGeneratedTokenHere",
"auth-header": "JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NTY3ODkwLCJleHAiOjE3MzE2ODI3Mjh9.OTPGeneratedTokenHere"
}
Authenticates using a one-time password (OTP) instead of username/password credentials. OTPs are typically sent via email or generated by an authenticator app.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| otp | string | Yes | The one-time password token. |
Response Fields
Same as /auth/jwt.
Error Responses
| Status | Description |
|---|---|
| 401 | OTP is invalid or has expired. |
/auth/reset-password [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/auth/reset-password" \
-H "Content-Type: application/json" \
-d '{
"username": "john.doe@example.com"
}'
Request body
{
"username": "john.doe@example.com"
}
Response (200 OK)
{
"message": "Password reset email sent."
}
Sends a password reset email to the user. The email contains a link to set a new password.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| username | string | Yes | The username or email address of the account. |
Error Responses
| Status | Description |
|---|---|
| 404 | User not found. |
/auth/change-password [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/auth/change-password" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"username": "john.doe@example.com",
"old_password": "s3cur3P@ssw0rd",
"new_password": "N3wS3cur3P@ss!"
}'
Request body
{
"username": "john.doe@example.com",
"old_password": "s3cur3P@ssw0rd",
"new_password": "N3wS3cur3P@ss!"
}
Response (200 OK)
{
"message": "Password changed successfully."
}
Changes the password for the authenticated user. The new password is validated for sufficient strength.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| username | string | Yes | The username of the account. |
| old_password | string | Yes | The current password. |
| new_password | string | Yes | The new password. Must meet strength requirements. |
Error Responses
| Status | Description |
|---|---|
| 400 | New password does not meet strength requirements. |
| 401 | Old password is incorrect. |
/auth/logout [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/auth/logout" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"message": "Successfully logged out."
}
Logs out the current user by blacklisting their JWT token. The token will no longer be accepted for authentication after this call.
Error Responses
| Status | Description |
|---|---|
| 401 | Token is invalid or already blacklisted. |
/auth/me [GET]
Request
curl "https://httpapi.sustainder.com/v2/auth/me" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"name": "john.doe@example.com",
"applications": [
{
"artkey": 35,
"name": "Amsterdam Centrum",
"image": null,
"is_tilted_warning_threshold": 10.0,
"is_tilted_error_threshold": 45.0,
"permission_asset_management": true,
"permission_tilted": true
},
{
"artkey": 42,
"name": "Rotterdam Zuid",
"image": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk...",
"is_tilted_warning_threshold": 15.0,
"is_tilted_error_threshold": 45.0,
"permission_asset_management": false,
"permission_tilted": true
}
]
}
Returns information about the currently authenticated user and their available applications. Use the artkey value from the applications array as the application_artkey parameter in other API calls.
Response Fields
| Field | Type | Description |
|---|---|---|
| name | string | The authenticated user's username/email. |
| applications | array | List of applications the user has access to. |
| applications[].artkey | integer | The unique identifier for the application. Used as application_artkey in other endpoints. |
| applications[].name | string | The display name of the application/system. |
| applications[].image | string\ | null |
| applications[].is_tilted_warning_threshold | float | Tilt angle (degrees) at which a warning is triggered. |
| applications[].is_tilted_error_threshold | float | Tilt angle (degrees) at which an error is triggered. |
| applications[].permission_asset_management | boolean | Whether the user has asset management permissions for this application. |
| applications[].permission_tilted | boolean | Whether the user has permission to view tilt data. |
Error Responses
| Status | Description |
|---|---|
| 401 | Not authenticated or token expired. |
Nodes
Nodes represent physical entities in the network — typically a streetlight pole or mounting point. Each node has its own identity, location, and may host one or more services (LCM, Gateway, Sensors).
/nodes [GET]
Request
curl "https://httpapi.sustainder.com/v2/nodes?page=1" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/nodes"
}
},
"_embedded": {
"nodes": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/nodes/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
"nodes": {
"href": "https://httpapi.sustainder.com/v2/nodes"
},
"sensors_service": {
"href": "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
"lcm_service": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
},
"gateway_service": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289"
}
},
"_total": null,
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Keizersgracht 42",
"area": "Centrum",
"street": "Keizersgracht",
"status": "ONLINE",
"group_name": "Canal District",
"is_locked": false,
"longitude": 4.8879,
"latitude": 52.3702,
"software_version": "2.4.1",
"last_status_update": "2024-11-15T14:32:08Z",
"gateway_id": "352890061121289",
"lcm_service_id": "LCM-001234",
"gateway_service_id": "352890061121289"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/nodes/b2c3d4e5-f6a7-8901-bcde-f12345678901"
},
"nodes": {
"href": "https://httpapi.sustainder.com/v2/nodes"
},
"sensors_service": {
"href": "https://httpapi.sustainder.com/v2/sensors/b2c3d4e5-f6a7-8901-bcde-f12345678901"
},
"lcm_service": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001235"
}
},
"_total": null,
"node_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"name": "Herengracht 108",
"area": "Centrum",
"street": "Herengracht",
"status": "OFFLINE",
"group_name": "Canal District",
"is_locked": false,
"longitude": 4.8912,
"latitude": 52.3715,
"software_version": "2.4.0",
"last_status_update": "2024-11-14T23:45:12Z",
"gateway_id": "352890061121289",
"lcm_service_id": "LCM-001235",
"gateway_service_id": "352890061121289"
}
]
},
"_total": 128
}
Lists all nodes in your system. Supports pagination to split results into manageable chunks.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number (1-indexed). Request successive pages until _total returns 0. |
| service | string | - | Filter by service type: lcm, gateway, or sensor. |
Response Fields
| Field | Type | Description |
|---|---|---|
| node_id | string | Unique identifier for the node (UUID). |
| name | string | Display name of the node. |
| area | string | The area/district where the node is located. |
| street | string | The street where the node is located. |
| status | string | Online status: ONLINE or OFFLINE. |
| group_name | string | Name of the group this node belongs to. |
| is_locked | boolean | Whether the node is locked from changes. |
| longitude | float | GPS longitude coordinate. |
| latitude | float | GPS latitude coordinate. |
| software_version | string | Current firmware/software version. |
| last_status_update | string | ISO 8601 timestamp of the last status update. |
| gateway_id | string | ID of the gateway this node communicates through. |
| lcm_service_id | string | ID of the LCM service provided by this node. |
| gateway_service_id | string | ID of the gateway service (if this node is a gateway). |
/nodes/{node_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/nodes/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/nodes/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
"nodes": {
"href": "https://httpapi.sustainder.com/v2/nodes"
},
"sensors_service": {
"href": "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
"lcm_service": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
},
"gateway_service": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289"
}
},
"_embedded": {
"lcm_service": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
},
"lcms": {
"href": "https://httpapi.sustainder.com/v2/lcms"
}
},
"_total": null,
"lcm_id": "LCM-001234",
"model": "alexia",
"status": "ONLINE",
"lighting_mode": "DIMSCHEME",
"override_light_level": [0, 0, 0, 0],
"light_level": [100, 100, 100, 100],
"error_type": null,
"error_types": []
}
],
"gateway_service": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289"
}
},
"_total": null,
"gateway_id": "352890061121289",
"gateway_status": "ONLINE",
"timezone": "Europe/Amsterdam"
}
]
},
"_total": null,
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Keizersgracht 42",
"area": "Centrum",
"street": "Keizersgracht",
"status": "ONLINE",
"group_name": "Canal District",
"is_locked": false,
"longitude": 4.8879,
"latitude": 52.3702,
"software_version": "2.4.1",
"last_status_update": "2024-11-15T14:32:08Z",
"gateway_id": "352890061121289",
"lcm_service_id": "LCM-001234",
"gateway_service_id": "352890061121289"
}
Returns detailed information for a specific node, including embedded service data (LCM, gateway) when available.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| node_id | string | The UUID of the node. |
Response Fields
Same fields as the node list response, plus _embedded services:
| Embedded Resource | Description |
|---|---|
| lcm_service | LCM service details if the node has one. See LCM Details. |
| gateway_service | Gateway details if the node provides gateway service. See Gateway Details. |
Error Responses
| Status | Description |
|---|---|
| 404 | Node not found. |
/nodes/{node_id} [PATCH]
Request
curl -X PATCH "https://httpapi.sustainder.com/v2/nodes/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Keizersgracht 42 - Renovated",
"latitude": 52.3703,
"longitude": 4.8880
}'
Request body
{
"name": "Keizersgracht 42 - Renovated",
"latitude": 52.3703,
"longitude": 4.8880
}
Response (200 OK)
200 OK
Updates the display name and/or GPS location of a node. Send null for any field you don't want to change.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| node_id | string | The UUID of the node to update. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | No | New display name for the node. Max 30 characters. |
| latitude | float\ | null | No |
| longitude | float\ | null | No |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., name exceeds 30 characters). |
| 404 | Node not found. |
LCMs
This section covers Lighting Control Modules (LCMs) — the controllers inside luminaires that manage dimming, energy metering, and status reporting. LCMs support functions for direct control overrides, clearing overrides, previewing light levels, and settings for maximum light levels and dimming schemes.
/lcms [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms?page=1" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/lcms"
},
"direct_control": {
"href": "https://httpapi.sustainder.com/v2/lcms/functions/direct-control"
},
"preview_light_levels": {
"href": "https://httpapi.sustainder.com/v2/lcms/functions/preview-light-levels"
},
"set_light_levels": {
"href": "https://httpapi.sustainder.com/v2/lcms/settings/light-levels"
},
"set_dimming_scheme": {
"href": "https://httpapi.sustainder.com/v2/lcms/settings/dimming-scheme"
}
},
"_embedded": {
"lcms": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
},
"lcms": {
"href": "https://httpapi.sustainder.com/v2/lcms"
}
},
"_total": null,
"lcm_id": "LCM-001234",
"model": "alexia",
"status": "ONLINE",
"pole_number": "P-042",
"lighting_mode": "DIMSCHEME",
"override_light_level": [0, 0, 0, 0],
"light_level": [100, 100, 100, 100],
"latest_energy_kwh": 1247.3,
"latest_energy_kwh_timestamp": "2024-11-15T14:00:00Z",
"latest_power_watt": 42.5,
"latest_power_watt_timestamp": "2024-11-15T14:00:00Z",
"relative_tilt_angle": 2.1,
"last_sensor_data_update": "2024-11-15T14:00:00Z",
"ambient": 0.8,
"temperature_internal": 18.5,
"latest_running_hours": 12450,
"latest_running_hours_timestamp": "2024-11-15T14:00:00Z",
"dimming_calendar_name": "Winter Schedule",
"error_type": null,
"error_types": [],
"details": {
"production_date": "2023-03-15",
"driver_type": "DALI-2",
"lumen_output": 4500,
"watt": 45,
"armature_color": "RAL 7016",
"light_color": "3000K",
"clo": "enabled",
"optics": "Wide Street",
"dimming_scheme": "Standard Evening",
"mounting_diameter": "60mm",
"guard": "none",
"tilt_angle": "0",
"cable_type": "5G2.5",
"cable_length": "1.5m",
"optic_addon": null,
"last_sensor_data_update": "2024-11-15T14:00:00Z"
}
}
]
},
"_total": 128
}
Lists all LCMs in your system with pagination support.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number (1-indexed). Request successive pages until _total returns 0. |
Response Fields
| Field | Type | Description |
|---|---|---|
| lcm_id | string | The unique LCM identifier. |
| model | string | The LCM model (e.g., alexia, anne, bianca). |
| status | string | Online status: ONLINE or OFFLINE. |
| pole_number | string | The pole number where this LCM is mounted. |
| lighting_mode | string | Current lighting mode: DIMSCHEME, DIRECT_CONTROL, MOTION, or AMBIENT. |
| override_light_level | array | Current override light levels for each channel [c1, c2, c3, c4] (0-100). |
| light_level | array | Maximum configured light levels [c1, c2, c3, c4] (0-100). |
| latest_energy_kwh | float | Latest measured energy consumption in kWh. |
| latest_energy_kwh_timestamp | string | ISO 8601 timestamp of the energy measurement. |
| latest_power_watt | float | Latest measured power consumption in Watts. |
| latest_power_watt_timestamp | string | ISO 8601 timestamp of the power measurement. |
| relative_tilt_angle | float | Tilt angle in degrees relative to the horizontal plane. |
| last_sensor_data_update | string | ISO 8601 timestamp of the last sensor data received. |
| ambient | float | Ambient light level measured by the LCM. |
| temperature_internal | float | Internal temperature of the LCM in degrees Celsius. |
| latest_running_hours | integer | Total running hours of the LCM. |
| latest_running_hours_timestamp | string | ISO 8601 timestamp of the running hours measurement. |
| dimming_calendar_name | string | Name of the currently active dimming calendar. |
| error_type | string\ | null |
| error_types | array | List of all active error types. Empty if no errors. |
| details | object\ | null |
Details Object
| Field | Type | Description |
|---|---|---|
| production_date | string | Date of manufacture (ISO format). |
| driver_type | string | LED driver type (e.g., DALI-2). |
| lumen_output | integer | Luminaire output in lumens. |
| watt | integer | Rated wattage. |
| armature_color | string | Color code of the armature (e.g., RAL 7016). |
| light_color | string | Color temperature (e.g., 3000K). |
| clo | string | Constant Light Output status. |
| optics | string | Optics type installed. |
| dimming_scheme | string | Factory dimming scheme name. |
| mounting_diameter | string | Mounting post diameter. |
| guard | string | Guard type installed. |
| tilt_angle | string | Factory tilt angle setting. |
| cable_type | string | Cable specification. |
| cable_length | string | Cable length. |
| optic_addon | string\ | null |
/lcms/{lcm_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
},
"lcms": {
"href": "https://httpapi.sustainder.com/v2/lcms"
}
},
"_embedded": {
"node": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/nodes/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
"nodes": {
"href": "https://httpapi.sustainder.com/v2/nodes"
},
"sensors_service": {
"href": "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
"lcm_service": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
},
"gateway_service": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289"
}
},
"_total": null,
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Keizersgracht 42",
"area": "Centrum",
"street": "Keizersgracht",
"status": "ONLINE",
"group_name": "Canal District",
"is_locked": false,
"longitude": 4.8879,
"latitude": 52.3702,
"software_version": "2.4.1",
"last_status_update": "2024-11-15T14:32:08Z",
"gateway_id": "352890061121289",
"lcm_service_id": "LCM-001234",
"gateway_service_id": "352890061121289"
}
]
},
"_total": null,
"lcm_id": "LCM-001234",
"model": "alexia",
"status": "ONLINE",
"pole_number": "P-042",
"lighting_mode": "DIMSCHEME",
"override_light_level": [0, 0, 0, 0],
"light_level": [100, 100, 100, 100],
"latest_energy_kwh": 1247.3,
"latest_energy_kwh_timestamp": "2024-11-15T14:00:00Z",
"latest_power_watt": 42.5,
"latest_power_watt_timestamp": "2024-11-15T14:00:00Z",
"relative_tilt_angle": 2.1,
"last_sensor_data_update": "2024-11-15T14:00:00Z",
"ambient": 0.8,
"temperature_internal": 18.5,
"latest_running_hours": 12450,
"latest_running_hours_timestamp": "2024-11-15T14:00:00Z",
"dimming_calendar_name": "Winter Schedule",
"error_type": null,
"error_types": [],
"details": null
}
Returns detailed information for a specific LCM, including the embedded node data.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
Error Responses
| Status | Description |
|---|---|
| 404 | LCM not found. |
/lcms/{lcm_id}/status [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234/status" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (202 Accepted)
{
"message": "Status request sent."
}
Requests a fresh status update from the LCM. The status is fetched asynchronously — the LCM will respond when it can. Use webhooks to receive the status response, or poll the LCM details endpoint after a short delay.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
Error Responses
| Status | Description |
|---|---|
| 404 | LCM not found. |
| 504 | LCM is offline and cannot receive the status request. |
/lcms/{lcm_id}/sensor_status [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234/sensor_status" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (202 Accepted)
{
"message": "Sensor status request sent."
}
Requests a fresh sensor data update from the LCM. This triggers the LCM to report its latest sensor readings (energy, power, temperature, ambient, tilt, etc.).
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
/lcms/{lcm_id}/zhaga-status [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234/zhaga-status" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (202 Accepted)
{
"message": "Zhaga diagnostics request sent."
}
Requests Zhaga diagnostics data from the LCM. Zhaga is a standardized interface for smart luminaire components.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
/lcms/functions/clear-override [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/lcms/functions/clear-override" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"lcm_ids": ["LCM-001234", "LCM-001235"],
"application_artkey": 35
}'
Request body
{
"lcm_ids": ["LCM-001234", "LCM-001235"],
"application_artkey": 35
}
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/lcms/functions/clear-override"
}
},
"_total": null,
"lcms_ok": ["LCM-001234"],
"lcms_nok": [
{
"lcm_id": "LCM-001235",
"message": "Device is offline"
}
]
}
Clears a direct control override on one or more LCMs, returning them to their standard dimming scheme.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| lcm_ids | array | Yes | List of LCM IDs to clear overrides for. |
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| lcms_ok | array | LCM IDs that successfully received the clear command. |
| lcms_nok | array | LCMs that failed. Each entry contains lcm_id and message explaining the failure. |
/lcms/functions/direct-control [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/lcms/functions/direct-control" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"override": [
{
"lcm_id": "LCM-001234",
"override": [80, 0, 0, 0],
"duration": 30
},
{
"lcm_id": "LCM-001235",
"override": [100, 100, 100, 100]
}
],
"application_artkey": 35
}'
Request body
{
"override": [
{
"lcm_id": "LCM-001234",
"override": [80, 0, 0, 0],
"duration": 30
},
{
"lcm_id": "LCM-001235",
"override": [100, 100, 100, 100]
}
],
"application_artkey": 35
}
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/lcms/functions/direct-control"
}
},
"_total": null,
"lcms_ok": ["LCM-001234", "LCM-001235"],
"lcms_nok": [],
"application_artkey": 35
}
Sets an override light level on one or more LCMs, bypassing the active dimming scheme. The override can be permanent or time-limited.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| override | array | Yes | List of override objects. |
| override[].lcm_id | string | Yes | The LCM to override. |
| override[].override | array | Yes | Light levels for 4 channels: [c1, c2, c3, c4]. Values 0-100. |
| override[].duration | integer | No | Duration in minutes (max 240 / 4 hours). |
| application_artkey | integer | Yes | The application identifier. |
If duration is not set, the override remains active until:
- Cleared with the clear override command.
- The luminaire is power cycled.
- A user selects "Set to defaults" in the Sustainder Light App.
- A new override with a
durationis sent (replaces the current one for that duration).
Response Fields
Same as clear override response.
/lcms/functions/preview-light-levels [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/lcms/functions/preview-light-levels" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"light_levels": [
{
"lcm_id": "LCM-001234",
"light_levels": [75, 75, 75, 75]
}
],
"application_artkey": 35
}'
Request body
{
"light_levels": [
{
"lcm_id": "LCM-001234",
"light_levels": [75, 75, 75, 75]
}
],
"application_artkey": 35
}
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/lcms/functions/preview-light-levels"
}
},
"_total": null,
"lcms_ok": ["LCM-001234"],
"lcms_nok": []
}
Temporarily shows what the new maximum light level would look like on the LCM. Similar to direct control but displays the effect of changing light levels rather than setting an override.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| light_levels | array | Yes | List of preview objects. |
| light_levels[].lcm_id | string | Yes | The LCM to preview. |
| light_levels[].light_levels | array | Yes | Preview levels [c1, c2, c3, c4]. Values 0-100. |
| application_artkey | integer | Yes | The application identifier. |
/lcms/settings/light-levels [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/lcms/settings/light-levels" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"light_levels": [
{
"lcm_id": "LCM-001234",
"light_levels": [85, 85, 85, 85]
},
{
"lcm_id": "LCM-001235",
"light_levels": [100, 50, 100, 50]
}
],
"queue_when_offline": true,
"application_artkey": 35
}'
Request body
{
"light_levels": [
{
"lcm_id": "LCM-001234",
"light_levels": [85, 85, 85, 85]
},
{
"lcm_id": "LCM-001235",
"light_levels": [100, 50, 100, 50]
}
],
"queue_when_offline": true,
"application_artkey": 35
}
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/lcms/settings/light-levels"
}
},
"_total": null,
"lcms_ok": ["LCM-001234"],
"lcms_queued": ["LCM-001235"],
"lcms_nok": []
}
Sets the maximum light output levels for one or more LCMs. After this setting is applied, 100% in a dimming scheme or direct control will be scaled to these levels.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| light_levels | array | Yes | List of light level settings. |
| light_levels[].lcm_id | string | Yes | The LCM to configure. |
| light_levels[].light_levels | array | Yes | Maximum levels [c1, c2, c3, c4]. Values 0-100. |
| queue_when_offline | boolean | No | If true, queue the setting for offline devices. Default: false. |
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| lcms_ok | array | LCMs that received the setting immediately. |
| lcms_queued | array | LCMs that are offline but will receive the setting when they come online (only when queue_when_offline is true). |
| lcms_nok | array | LCMs that failed. Contains lcm_id and message. |
/lcms/settings/dimming-schemes [POST]
Request — Link a known dimming scheme
curl -X POST "https://httpapi.sustainder.com/v2/lcms/settings/dimming-schemes" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"lcms": [
{
"lcm_id": "LCM-001234",
"calendar_name": "Winter Schedule"
}
],
"calendars": [],
"dimming_schemes": [],
"queue_when_offline": true,
"application_artkey": 35
}'
Request — Create a transient dimming scheme
curl -X POST "https://httpapi.sustainder.com/v2/lcms/settings/dimming-schemes" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"lcms": [
{
"lcm_id": "LCM-001234",
"calendar_name": "Custom Weekday Calendar"
}
],
"calendars": [
{
"calendar_name": "Custom Weekday Calendar",
"dimming_scheme_rules": [
{
"dimming_scheme_name": "Weekday Evening",
"days": ["mo", "tu", "we", "th", "fr"]
},
{
"dimming_scheme_name": "Weekend Evening",
"days": ["sa", "su"]
}
]
}
],
"dimming_schemes": [
{
"dimming_scheme_name": "Weekday Evening",
"steps": [
{"light_level": 100, "sunset": "-12"},
{"light_level": 75, "time": "22:00"},
{"light_level": 50, "time": "00:00"},
{"light_level": 100, "time": "05:30"},
{"light_level": 0, "sunrise": "9"}
]
},
{
"dimming_scheme_name": "Weekend Evening",
"steps": [
{"light_level": 100, "sunset": "-12"},
{"light_level": 85, "time": "23:00"},
{"light_level": 60, "time": "01:00"},
{"light_level": 100, "time": "06:00"},
{"light_level": 0, "sunrise": "9"}
]
}
],
"queue_when_offline": true,
"application_artkey": 35
}'
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/lcms/settings/dimming-schemes"
}
},
"_total": null,
"lcms_ok": ["LCM-001234"],
"lcms_queued": [],
"lcms_nok": []
}
Assigns a dimming scheme to one or more LCMs. You can either link a known dimming scheme (created via the Dimming Schemes API) or create a transient dimming scheme inline with day-specific schedules.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| lcms | array | Yes | LCM assignments. |
| lcms[].lcm_id | string | Yes | The LCM to assign the scheme to. |
| lcms[].calendar_name | string | Yes | Name of the known scheme or transient calendar. |
| calendars | array | No | Transient calendar definitions (leave empty for known schemes). |
| calendars[].calendar_name | string | Yes | Calendar name matching the LCM assignment. |
| calendars[].dimming_scheme_rules | array | Yes | Day-to-scheme mappings. |
| calendars[].dimming_scheme_rules[].dimming_scheme_name | string | Yes | Scheme name to apply. |
| calendars[].dimming_scheme_rules[].days | array | Yes | Days of the week: mo, tu, we, th, fr, sa, su. |
| dimming_schemes | array | No | Transient scheme definitions (leave empty for known schemes). |
| dimming_schemes[].dimming_scheme_name | string | Yes | Scheme name matching calendar rules. |
| dimming_schemes[].steps | array | Yes | Dimming steps. See Dimming Schemes for step format. |
| queue_when_offline | boolean | No | If true, queue for offline devices. Default: false. |
| application_artkey | integer | Yes | The application identifier. |
Response Fields
Same as light levels response.
Groups
Groups couple multiple nodes into a single controllable unit. Nodes can only belong to one group at a time — adding a node to a new group removes it from its current group. Groups support the same control operations as individual LCMs (direct control, clear override, light levels).
/groups [GET]
Request
curl "https://httpapi.sustainder.com/v2/groups" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/groups"
}
},
"_embedded": {
"groups": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/groups/28"
},
"groups": {
"href": "https://httpapi.sustainder.com/v2/groups"
}
},
"_total": null,
"group_id": "28",
"name": "Canal District",
"status": "complete",
"node_ids": [
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"b2c3d4e5-f6a7-8901-bcde-f12345678901"
]
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/groups/29"
},
"groups": {
"href": "https://httpapi.sustainder.com/v2/groups"
}
},
"_total": null,
"group_id": "29",
"name": "Vondelpark Paths",
"status": "queued",
"node_ids": [
"c3d4e5f6-a7b8-9012-cdef-123456789012"
]
}
]
},
"_total": null
}
Lists all groups registered in your system.
Response Fields
| Field | Type | Description |
|---|---|---|
| group_id | string | Unique group identifier. |
| name | string | Display name of the group. |
| status | string | Installation status (see table below). |
| node_ids | array | List of node IDs belonging to this group. |
Group Status Values
| Status | Description |
|---|---|
queued |
Group created but not yet communicated to the nodes. |
complete |
All nodes have been updated and the group is fully operational. |
queued_deletion |
Deletion command has not yet been communicated to all nodes. |
/groups [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/groups" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"group_name": "Prinsengracht East",
"node_ids": [
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"b2c3d4e5-f6a7-8901-bcde-f12345678901"
],
"application_artkey": 35
}'
Request body
{
"group_name": "Prinsengracht East",
"node_ids": [
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"b2c3d4e5-f6a7-8901-bcde-f12345678901"
],
"application_artkey": 35
}
Response (202 Accepted)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/groups/30"
}
},
"group_name": "Prinsengracht East",
"group_id": "30",
"status": "queued",
"node_ids": [
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"b2c3d4e5-f6a7-8901-bcde-f12345678901"
]
}
Creates a new group. The group creation is queued and will become complete once all nodes have been updated.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| group_name | string | Yes | Display name for the group. |
| node_ids | array | Yes | List of node IDs to include in the group. |
| application_artkey | integer | Yes | The application identifier. |
/groups/{group_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/groups/28" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/groups/28"
},
"groups": {
"href": "https://httpapi.sustainder.com/v2/groups"
}
},
"_embedded": {
"nodes": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/nodes/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
"nodes": {
"href": "https://httpapi.sustainder.com/v2/nodes"
},
"sensors_service": {
"href": "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
"lcm_service": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
},
"gateway_service": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289"
}
},
"_total": null,
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Keizersgracht 42",
"status": "ONLINE",
"longitude": 4.8879,
"latitude": 52.3702,
"last_status_update": "2024-11-15T14:32:08Z",
"gateway_id": "352890061121289",
"lcm_service_id": "LCM-001234",
"gateway_service_id": "352890061121289"
}
]
},
"_total": null,
"group_id": "28",
"name": "Canal District",
"status": "complete",
"node_ids": [
"a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"b2c3d4e5-f6a7-8901-bcde-f12345678901"
]
}
Returns group details with embedded node data.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| group_id | string | The group identifier. |
Error Responses
| Status | Description |
|---|---|
| 404 | Group not found. |
/groups/{group_id} [DELETE]
Request
curl -X DELETE "https://httpapi.sustainder.com/v2/groups/28" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
200 OK
Deletes an existing group. The deletion is queued and the nodes will be updated asynchronously.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| group_id | string | The group identifier. |
/groups/migrations [GET]
Request
curl "https://httpapi.sustainder.com/v2/groups/migrations" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/groups/migrations"
}
},
"migrations": [
{
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"current_group": "Canal District",
"desired_group": "Centrum Zone A",
"status": "pending"
}
]
}
Returns information about pending group migrations — nodes that are in the process of moving between groups.
/groups/{group_id}/devices/{device_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/groups/28/devices/LCM-001234" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"device_id": "LCM-001234",
"group_id": "28",
"group_name": "Canal District",
"status": "ONLINE",
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
Returns information about a specific device within a group.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| group_id | string | The group identifier. |
| device_id | string | The device/LCM identifier. |
/groups/functions/direct-control [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/groups/functions/direct-control" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"override": [
{
"group_id": "28",
"override": [80, 0, 0, 0],
"duration": 60
}
],
"application_artkey": 35
}'
Request body
{
"override": [
{
"group_id": "28",
"override": [80, 0, 0, 0],
"duration": 60
}
],
"application_artkey": 35
}
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/groups/functions/direct-control"
}
},
"_total": null,
"groups_ok": ["28"],
"groups_nok": []
}
Sets a direct control override on all LCMs in one or more groups. This is the group variant of the LCM direct control command.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| override | array | Yes | List of group override objects. |
| override[].group_id | string | Yes | The group to override. |
| override[].override | array | Yes | Light levels [c1, c2, c3, c4]. Values 0-100. |
| override[].duration | integer | No | Duration in minutes (max 240). |
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| groups_ok | array | Groups that received the command successfully. |
| groups_nok | array | Groups that failed, with error details. |
/groups/functions/direct-control-all [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/groups/functions/direct-control-all" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"override": [80, 0, 0, 0],
"duration": 60,
"application_artkey": 35
}'
Request body
{
"override": [80, 0, 0, 0],
"duration": 60,
"application_artkey": 35
}
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/groups/functions/direct-control-all"
}
},
"_total": null,
"groups_ok": ["28", "29", "30"],
"groups_nok": []
}
Applies a direct control override to all groups in the application simultaneously.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| override | array | Yes | Light levels [c1, c2, c3, c4]. Values 0-100. Applied to all groups. |
| duration | integer | No | Duration in minutes (max 240). |
| application_artkey | integer | Yes | The application identifier. |
/groups/functions/clear-override [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/groups/functions/clear-override" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"group_ids": ["28", "29"],
"application_artkey": 35
}'
Request body
{
"group_ids": ["28", "29"],
"application_artkey": 35
}
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/groups/functions/clear-override"
}
},
"_total": null,
"groups_ok": ["28", "29"],
"groups_nok": []
}
Clears direct control overrides from one or more groups, returning all LCMs in those groups to their dimming schemes. Group variant of LCM clear override.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| group_ids | array | Yes | List of group IDs to clear overrides for. |
| application_artkey | integer | Yes | The application identifier. |
/groups/functions/clear-override-all [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/groups/functions/clear-override-all" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"application_artkey": 35
}'
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/groups/functions/clear-override-all"
}
},
"_total": null,
"groups_ok": ["28", "29", "30"],
"groups_nok": []
}
Clears direct control overrides from all groups in the application.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
/groups/settings/light-levels [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/groups/settings/light-levels" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"light_levels": [
{
"group_id": "28",
"light_levels": [85, 85, 85, 85]
}
],
"queue_when_offline": true,
"application_artkey": 35
}'
Request body
{
"light_levels": [
{
"group_id": "28",
"light_levels": [85, 85, 85, 85]
}
],
"queue_when_offline": true,
"application_artkey": 35
}
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/groups/settings/light-levels"
}
},
"_total": null,
"groups_ok": ["28"],
"groups_queued": [],
"groups_nok": []
}
Sets the maximum light levels for all LCMs in one or more groups. Group variant of LCM light levels.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| light_levels | array | Yes | Group light level settings. |
| light_levels[].group_id | string | Yes | The group to configure. |
| light_levels[].light_levels | array | Yes | Maximum levels [c1, c2, c3, c4]. Values 0-100. |
| queue_when_offline | boolean | No | If true, queue for offline devices. Default: false. |
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| groups_ok | array | Groups where all devices received the setting. |
| groups_queued | array | Groups with offline devices where settings were queued. |
| groups_nok | array | Groups that failed, with error details. |
Gateways
Gateways are the communication hubs that sit between nodes and the SBL. They provide internet connectivity for LCMs via RF (radio frequency). A single gateway can control up to 250 LCMs.
/gateways [GET]
Request
curl "https://httpapi.sustainder.com/v2/gateways" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/gateways"
}
},
"_embedded": {
"gateways": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289"
},
"gateways": {
"href": "https://httpapi.sustainder.com/v2/gateways"
}
},
"_total": null,
"gateway_id": "352890061121289",
"gateway_status": "ONLINE",
"timezone": "Europe/Amsterdam"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121345"
},
"gateways": {
"href": "https://httpapi.sustainder.com/v2/gateways"
}
},
"_total": null,
"gateway_id": "352890061121345",
"gateway_status": "OFFLINE",
"timezone": "Europe/Amsterdam"
}
]
},
"_total": 2
}
Lists all gateways in your system.
Response Fields
| Field | Type | Description |
|---|---|---|
| gateway_id | string | The unique gateway identifier (typically an IMEI number). |
| gateway_status | string | Online status: ONLINE or OFFLINE. |
| timezone | string | The timezone of the gateway (e.g., Europe/Amsterdam). Used for dimming scheme sunset/sunrise calculations. |
/gateways/{gateway_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/gateways/352890061121289" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289"
},
"gateways": {
"href": "https://httpapi.sustainder.com/v2/gateways"
}
},
"_total": null,
"gateway_id": "352890061121289",
"gateway_status": "ONLINE",
"timezone": "Europe/Amsterdam"
}
Returns details for a specific gateway.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| gateway_id | string | The gateway identifier. |
Error Responses
| Status | Description |
|---|---|
| 404 | Gateway not found. |
/gateways/{gateway_id}/sunsetsunrise [GET]
Request
curl "https://httpapi.sustainder.com/v2/gateways/352890061121289/sunsetsunrise" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289/sunsetsunrise"
}
},
"_embedded": {
"switch_moments": [
{
"_total": null,
"timestamp": "2024-11-15T12:00:00Z",
"sunset": "2024-11-15T16:32:00Z",
"sunrise": "2024-11-16T07:48:00Z"
},
{
"_total": null,
"timestamp": "2024-11-16T12:00:00Z",
"sunset": "2024-11-16T16:30:00Z",
"sunrise": "2024-11-17T07:50:00Z"
},
{
"_total": null,
"timestamp": "2024-11-17T12:00:00Z",
"sunset": "2024-11-17T16:29:00Z",
"sunrise": "2024-11-18T07:51:00Z"
}
]
},
"_total": null
}
Returns the sunset and sunrise times used by this gateway for dimming scheme calculations. These times determine when sunset/sunrise-relative dimming steps activate.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| gateway_id | string | The gateway identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| timestamp | string | Start of the switch moment period (generally noon yyyy-MM-ddT12:00:00Z). Switch periods run from noon to noon the following day. |
| sunset | string | ISO 8601 timestamp of the sunset moment. Used for sunset-relative dimming steps. |
| sunrise | string | ISO 8601 timestamp of the sunrise moment. Used for sunrise-relative dimming steps. |
Error Responses
| Status | Description |
|---|---|
| 404 | Gateway not found. |
Sensors
Sensors provide measurement data from nodes — including energy consumption, power usage, temperature, ambient light, RF signal strength, tilt angle, and more. The API returns the latest value for each sensor.
/sensors/{node_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
"node": {
"href": "https://httpapi.sustainder.com/v2/nodes/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
},
"_embedded": {
"sensors": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890/rf_strength"
}
},
"_total": null,
"sensor_name": "rf_strength",
"value": "-42",
"unit": "dBm",
"last_updated": "2024-11-15T14:32:08Z"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890/power"
}
},
"_total": null,
"sensor_name": "power",
"value": "42.5",
"unit": "W",
"last_updated": "2024-11-15T14:00:00Z"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890/temperature"
}
},
"_total": null,
"sensor_name": "temperature",
"value": "18.5",
"unit": "°C",
"last_updated": "2024-11-15T14:00:00Z"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890/ambient"
}
},
"_total": null,
"sensor_name": "ambient",
"value": "0.8",
"unit": "lux",
"last_updated": "2024-11-15T14:00:00Z"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890/energy"
}
},
"_total": null,
"sensor_name": "energy",
"value": "1247.3",
"unit": "kWh",
"last_updated": "2024-11-15T14:00:00Z"
}
],
"node": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/nodes/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
},
"nodes": {
"href": "https://httpapi.sustainder.com/v2/nodes"
},
"lcm_service": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
},
"gateway_service": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289"
}
},
"_total": null,
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Keizersgracht 42",
"status": "ONLINE",
"longitude": 4.8879,
"latitude": 52.3702,
"last_status_update": "2024-11-15T14:32:08Z",
"gateway_id": "352890061121289",
"lcm_service_id": "LCM-001234",
"gateway_service_id": "352890061121289"
}
]
},
"_total": null
}
Lists all sensors attached to a specific node, including the embedded node data.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| node_id | string | The UUID of the node. |
Common Sensor Types
| Sensor Name | Unit | Description |
|---|---|---|
| rf_strength | dBm | RF signal strength to the gateway. |
| power | W | Current power consumption in Watts. |
| energy | kWh | Cumulative energy consumption. |
| temperature | °C | Internal temperature. |
| ambient | lux | Ambient light level. |
| running_hours | hours | Total running hours. |
| tilt | degrees | Tilt angle relative to horizontal. |
Error Responses
| Status | Description |
|---|---|
| 404 | Node not found or has no sensors. |
/sensors/{node_id}/{sensor_name} [GET]
Request
curl "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890/power" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890/power"
},
"sensors": {
"href": "https://httpapi.sustainder.com/v2/sensors/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
},
"_total": null,
"sensor_name": "power",
"value": "42.5",
"unit": "W",
"last_updated": "2024-11-15T14:00:00Z"
}
Returns the latest reading for a specific sensor on a node.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| node_id | string | The UUID of the node. |
| sensor_name | string | The sensor name (e.g., power, temperature, ambient). |
Response Fields
| Field | Type | Description |
|---|---|---|
| sensor_name | string | Name of the sensor. |
| value | string | The latest measured value. |
| unit | string | Unit of measurement (e.g., kWh, W, °C, lux, dBm, hours, degrees). |
| last_updated | string | ISO 8601 timestamp of when the sensor last reported this value. |
Error Responses
| Status | Description |
|---|---|
| 404 | Node or sensor not found. |
Dimming Schemes
Dimming schemes define time-based light level schedules for LCMs. A scheme consists of steps that specify a light level and a time trigger. The LCM transitions between steps throughout the night cycle.
Dimming Steps
Example dimming steps
"steps": [
{
"light_level": 100,
"sunset": "-12"
},
{
"light_level": 75,
"time": "21:00"
},
{
"light_level": 50,
"time": "00:00"
},
{
"light_level": 100,
"time": "05:00"
},
{
"light_level": 0,
"sunrise": "9"
}
]
Each step has a light level (0-100) and one of three time triggers:
| Trigger | Format | Description |
|---|---|---|
time |
HH:MM |
Absolute time (e.g., 21:00). |
sunset |
minutes | Minutes relative to sunset. Negative = before sunset (e.g., -12 = 12 minutes before sunset). |
sunrise |
minutes | Minutes relative to sunrise. Positive = after sunrise (e.g., 9 = 9 minutes after sunrise). |
The LCM maintains each light level from its trigger time until the next step. Sunset/sunrise times are calculated per gateway based on its location. See Sunset/Sunrise for the calculated times.
/dimming-schemes [GET]
Request
curl "https://httpapi.sustainder.com/v2/dimming-schemes" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/dimming-schemes"
}
},
"_embedded": {
"dimming_schemes": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/dimming-schemes/1"
}
},
"_total": null,
"dimming_scheme_id": 1,
"dimming_scheme_name": "Standard Evening",
"steps": [
{"light_level": 100, "sunset": "-12"},
{"light_level": 75, "time": "22:00"},
{"light_level": 50, "time": "00:00"},
{"light_level": 100, "time": "05:30"},
{"light_level": 0, "sunrise": "9"}
]
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/dimming-schemes/2"
}
},
"_total": null,
"dimming_scheme_id": 2,
"dimming_scheme_name": "Energy Saver",
"steps": [
{"light_level": 80, "sunset": "0"},
{"light_level": 40, "time": "23:00"},
{"light_level": 80, "time": "06:00"},
{"light_level": 0, "sunrise": "0"}
]
}
]
},
"_total": null
}
Lists all named dimming schemes in the system. These are the same schemes visible in the Sustainder app.
Response Fields
| Field | Type | Description |
|---|---|---|
| dimming_scheme_id | integer | Unique scheme identifier. |
| dimming_scheme_name | string | Display name of the scheme. |
| steps | array | List of dimming steps (see Dimming Steps above). |
/dimming-schemes-application [GET]
Request
curl "https://httpapi.sustainder.com/v2/dimming-schemes-application" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/dimming-schemes-application"
}
},
"_embedded": {
"dimming_schemes": [
{
"dimming_scheme_id": 1,
"dimming_scheme_name": "Standard Evening",
"steps": [
{"light_level": 100, "sunset": "-12"},
{"light_level": 75, "time": "22:00"},
{"light_level": 0, "sunrise": "9"}
]
}
]
},
"_total": null
}
Lists dimming schemes scoped to the current application only.
/dimming-schemes [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/dimming-schemes" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"dimming_scheme_name": "Weekend Special",
"steps": [
{"light_level": 100, "sunset": "-15"},
{"light_level": 85, "time": "23:00"},
{"light_level": 60, "time": "01:00"},
{"light_level": 100, "time": "06:00"},
{"light_level": 0, "sunrise": "5"}
]
}'
Request body
{
"dimming_scheme_name": "Weekend Special",
"steps": [
{"light_level": 100, "sunset": "-15"},
{"light_level": 85, "time": "23:00"},
{"light_level": 60, "time": "01:00"},
{"light_level": 100, "time": "06:00"},
{"light_level": 0, "sunrise": "5"}
]
}
Response (201 Created)
201 Created
Creates a new named dimming scheme. The scheme will appear in the Sustainder app and can be assigned to LCMs using /lcms/settings/dimming-schemes.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| dimming_scheme_name | string | Yes | Name for the dimming scheme. |
| steps | array | Yes | List of dimming steps. See Dimming Steps. |
/dimming-schemes/{dimming_scheme_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/dimming-schemes/1" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/dimming-schemes/1"
}
},
"_total": null,
"dimming_scheme_id": 1,
"dimming_scheme_name": "Standard Evening",
"steps": [
{"light_level": 100, "sunset": "-12"},
{"light_level": 75, "time": "22:00"},
{"light_level": 50, "time": "00:00"},
{"light_level": 100, "time": "05:30"},
{"light_level": 0, "sunrise": "9"}
]
}
Returns the details of a specific dimming scheme.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| dimming_scheme_id | integer | The scheme identifier. |
Error Responses
| Status | Description |
|---|---|
| 404 | Dimming scheme not found. |
/dimming-scheme-exceptions [GET]
Request
curl "https://httpapi.sustainder.com/v2/dimming-scheme-exceptions" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/dimming-scheme-exceptions"
}
},
"_embedded": {
"exceptions": [
{
"artkey": 1,
"name": "New Year's Eve",
"date": "2024-12-31",
"dimming_scheme_id": 3,
"dimming_scheme_name": "Full Brightness Night"
},
{
"artkey": 2,
"name": "King's Day",
"date": "2025-04-27",
"dimming_scheme_id": 4,
"dimming_scheme_name": "Festival Mode"
}
]
},
"_total": 2
}
Lists all dimming scheme exceptions — special dates that override the normal dimming schedule (e.g., holidays, events).
Response Fields
| Field | Type | Description |
|---|---|---|
| artkey | integer | Unique exception identifier. |
| name | string | Description of the exception. |
| date | string | Date when the exception applies (YYYY-MM-DD). |
| dimming_scheme_id | integer | The dimming scheme to use on this date. |
| dimming_scheme_name | string | Name of the exception dimming scheme. |
/dimming-scheme-exceptions/{exception_artkey} [GET]
Request
curl "https://httpapi.sustainder.com/v2/dimming-scheme-exceptions/1" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"artkey": 1,
"name": "New Year's Eve",
"date": "2024-12-31",
"dimming_scheme_id": 3,
"dimming_scheme_name": "Full Brightness Night"
}
Returns details for a specific dimming scheme exception.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| exception_artkey | integer | The exception identifier. |
/dimming-scheme-schedules [GET]
Request
curl "https://httpapi.sustainder.com/v2/dimming-scheme-schedules" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/dimming-scheme-schedules"
}
},
"_embedded": {
"schedules": [
{
"artkey": 10,
"name": "Winter 2024-2025",
"start_date": "2024-11-01",
"end_date": "2025-03-31",
"status": "active",
"dimming_scheme_id": 1,
"dimming_scheme_name": "Standard Evening"
}
]
},
"_total": 1
}
Lists all dimming scheme schedules — date-range based schedule assignments.
/dimming-scheme-schedules/{schedule_artkey} [GET]
Request
curl "https://httpapi.sustainder.com/v2/dimming-scheme-schedules/10" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"artkey": 10,
"name": "Winter 2024-2025",
"start_date": "2024-11-01",
"end_date": "2025-03-31",
"status": "active",
"dimming_scheme_id": 1,
"dimming_scheme_name": "Standard Evening"
}
Returns details for a specific dimming scheme schedule.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| schedule_artkey | integer | The schedule identifier. |
/dimming-scheme-schedules/{schedule_artkey}/apply [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/dimming-scheme-schedules/10/apply" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"message": "Schedule applied successfully.",
"artkey": 10
}
Applies a dimming scheme schedule, pushing the scheduled dimming scheme to all applicable devices.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| schedule_artkey | integer | The schedule identifier to apply. |
Error Responses
| Status | Description |
|---|---|
| 404 | Schedule not found. |
Motion Configuration
Motion configs manage motion-based lighting -- LCMs react to motion detected on road sections. When a motion sensor detects activity, the system triggers nearby luminaires to increase their light output, creating a wave of light that follows movement along the road. Each motion config defines which devices participate, how they are grouped into road sections, and the trigger behavior that governs the lighting response.
/motion-configs [GET]
Request
curl "https://httpapi.sustainder.com/v2/motion-configs?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs?application_artkey=35"
}
},
"_embedded": {
"motion_configs": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12"
},
"motion_configs": {
"href": "https://httpapi.sustainder.com/v2/motion-configs"
}
},
"_total": null,
"motion_config_id": 12,
"name": "Keizersgracht North",
"description": "Motion lighting for the northern section of Keizersgracht",
"enabled": true,
"created_at": "2024-08-10T09:15:00Z",
"updated_at": "2025-01-20T14:30:00Z"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/15"
},
"motion_configs": {
"href": "https://httpapi.sustainder.com/v2/motion-configs"
}
},
"_total": null,
"motion_config_id": 15,
"name": "Herengracht Cycle Path",
"description": "Motion-triggered lighting along the cycle path",
"enabled": true,
"created_at": "2024-09-05T11:20:00Z",
"updated_at": "2025-02-12T08:45:00Z"
}
]
},
"_total": 2
}
Lists all motion configurations for a given application.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier to list motion configs for. |
Response Fields
| Field | Type | Description |
|---|---|---|
| motion_config_id | integer | Unique identifier for the motion configuration. |
| name | string | Human-readable name of the motion config. |
| description | string | Description of the motion config purpose or location. |
| enabled | boolean | Whether the motion config is currently active. |
| created_at | string | ISO 8601 timestamp of when the config was created. |
| updated_at | string | ISO 8601 timestamp of when the config was last modified. |
/motion-configs/{motion_config_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/motion-configs/12" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12"
},
"motion_configs": {
"href": "https://httpapi.sustainder.com/v2/motion-configs"
},
"device_triggers": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/device-triggers"
},
"road_sections": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/road-sections"
},
"devices": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/devices"
}
},
"_total": null,
"motion_config_id": 12,
"name": "Keizersgracht North",
"description": "Motion lighting for the northern section of Keizersgracht",
"enabled": true,
"motion_intensity": 80,
"motion_duration": 120,
"fade_in_time": 3,
"fade_out_time": 10,
"base_light_level": 30,
"triggered_light_level": 100,
"road_section_count": 4,
"device_count": 16,
"created_at": "2024-08-10T09:15:00Z",
"updated_at": "2025-01-20T14:30:00Z"
}
Returns detailed information for a specific motion configuration, including timing and light-level parameters.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| motion_config_id | integer | The motion configuration identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| motion_config_id | integer | Unique identifier for the motion configuration. |
| name | string | Human-readable name. |
| description | string | Description of the config. |
| enabled | boolean | Whether the motion config is currently active. |
| motion_intensity | integer | Sensitivity of motion detection (0-100). |
| motion_duration | integer | Duration in seconds that the triggered light level is held after motion is detected. |
| fade_in_time | integer | Time in seconds for the light to ramp up to the triggered level. |
| fade_out_time | integer | Time in seconds for the light to ramp down to the base level. |
| base_light_level | integer | The dimmed light level (0-100) when no motion is detected. |
| triggered_light_level | integer | The light level (0-100) when motion is detected. |
| road_section_count | integer | Number of road sections in this config. |
| device_count | integer | Total number of devices in this config. |
| created_at | string | ISO 8601 timestamp of creation. |
| updated_at | string | ISO 8601 timestamp of last modification. |
Error Responses
| Status | Description |
|---|---|
| 404 | Motion config not found. |
/motion-configs/{motion_config_id}/device-triggers [GET]
Request
curl "https://httpapi.sustainder.com/v2/motion-configs/12/device-triggers" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/device-triggers"
},
"motion_config": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12"
}
},
"_embedded": {
"device_triggers": [
{
"_total": null,
"device_trigger_id": 201,
"lcm_id": "LCM-001234",
"pole_number": "P-042",
"trigger_type": "PIR",
"trigger_range_meters": 15,
"trigger_direction": "bidirectional",
"road_section_id": 50,
"enabled": true
},
{
"_total": null,
"device_trigger_id": 202,
"lcm_id": "LCM-001235",
"pole_number": "P-043",
"trigger_type": "PIR",
"trigger_range_meters": 15,
"trigger_direction": "bidirectional",
"road_section_id": 50,
"enabled": true
},
{
"_total": null,
"device_trigger_id": 203,
"lcm_id": "LCM-001240",
"pole_number": "P-048",
"trigger_type": "RADAR",
"trigger_range_meters": 25,
"trigger_direction": "forward",
"road_section_id": 51,
"enabled": false
}
]
},
"_total": 3
}
Returns all device triggers associated with a motion configuration. Device triggers define which sensors on which LCMs detect motion and how far ahead they activate neighboring luminaires.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| motion_config_id | integer | The motion configuration identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| device_trigger_id | integer | Unique identifier for the device trigger. |
| lcm_id | string | The LCM that hosts this trigger sensor. |
| pole_number | string | The pole number where the LCM is mounted. |
| trigger_type | string | Sensor type: PIR (passive infrared) or RADAR. |
| trigger_range_meters | integer | Detection range in meters. |
| trigger_direction | string | Direction of triggering: forward, backward, or bidirectional. |
| road_section_id | integer | The road section this trigger belongs to. |
| enabled | boolean | Whether the trigger is active. |
Error Responses
| Status | Description |
|---|---|
| 404 | Motion config not found. |
/motion-configs/{motion_config_id}/road-sections [GET]
Request
curl "https://httpapi.sustainder.com/v2/motion-configs/12/road-sections" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/road-sections"
},
"motion_config": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12"
}
},
"_embedded": {
"road_sections": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/road-sections/50"
}
},
"_total": null,
"road_section_id": 50,
"name": "Keizersgracht North - Segment A",
"device_count": 5,
"order": 1
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/road-sections/51"
}
},
"_total": null,
"road_section_id": 51,
"name": "Keizersgracht North - Segment B",
"device_count": 4,
"order": 2
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/road-sections/52"
}
},
"_total": null,
"road_section_id": 52,
"name": "Keizersgracht North - Segment C",
"device_count": 4,
"order": 3
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/road-sections/53"
}
},
"_total": null,
"road_section_id": 53,
"name": "Keizersgracht North - Segment D",
"device_count": 3,
"order": 4
}
]
},
"_total": 4
}
Lists all road sections within a motion configuration. Road sections represent contiguous stretches of road where devices are grouped together for coordinated motion-triggered lighting.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| motion_config_id | integer | The motion configuration identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| road_section_id | integer | Unique identifier for the road section. |
| name | string | Human-readable name of the road section. |
| device_count | integer | Number of devices in this road section. |
| order | integer | The sequential order of this section within the motion config. Determines the wave direction. |
Error Responses
| Status | Description |
|---|---|
| 404 | Motion config not found. |
/motion-configs/{motion_config_id}/devices [GET]
Request
curl "https://httpapi.sustainder.com/v2/motion-configs/12/devices" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/devices"
},
"motion_config": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12"
}
},
"_embedded": {
"devices": [
{
"_links": {
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
}
},
"_total": null,
"lcm_id": "LCM-001234",
"pole_number": "P-042",
"status": "ONLINE",
"road_section_id": 50,
"road_section_name": "Keizersgracht North - Segment A",
"position_in_section": 1,
"has_trigger": true,
"latitude": 52.3702,
"longitude": 4.8879
},
{
"_links": {
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001235"
}
},
"_total": null,
"lcm_id": "LCM-001235",
"pole_number": "P-043",
"status": "ONLINE",
"road_section_id": 50,
"road_section_name": "Keizersgracht North - Segment A",
"position_in_section": 2,
"has_trigger": true,
"latitude": 52.3705,
"longitude": 4.8882
},
{
"_links": {
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001236"
}
},
"_total": null,
"lcm_id": "LCM-001236",
"pole_number": "P-044",
"status": "ONLINE",
"road_section_id": 50,
"road_section_name": "Keizersgracht North - Segment A",
"position_in_section": 3,
"has_trigger": false,
"latitude": 52.3708,
"longitude": 4.8885
}
]
},
"_total": 16
}
Lists all devices (LCMs) participating in a motion configuration, across all road sections.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| motion_config_id | integer | The motion configuration identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
| pole_number | string | The pole number where the device is mounted. |
| status | string | Online status: ONLINE or OFFLINE. |
| road_section_id | integer | The road section this device belongs to. |
| road_section_name | string | Name of the road section. |
| position_in_section | integer | The device's position within the road section (1-indexed). Determines the lighting wave order. |
| has_trigger | boolean | Whether this device has a motion trigger sensor. |
| latitude | float | Latitude coordinate of the device. |
| longitude | float | Longitude coordinate of the device. |
Error Responses
| Status | Description |
|---|---|
| 404 | Motion config not found. |
/motion-configs/{motion_config_id}/road-sections/{road_section_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/motion-configs/12/road-sections/50" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/road-sections/50"
},
"motion_config": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12"
},
"road_sections": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/road-sections"
}
},
"_embedded": {
"devices": [
{
"_links": {
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
}
},
"_total": null,
"lcm_id": "LCM-001234",
"pole_number": "P-042",
"status": "ONLINE",
"position_in_section": 1,
"has_trigger": true,
"latitude": 52.3702,
"longitude": 4.8879
},
{
"_links": {
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001235"
}
},
"_total": null,
"lcm_id": "LCM-001235",
"pole_number": "P-043",
"status": "ONLINE",
"position_in_section": 2,
"has_trigger": true,
"latitude": 52.3705,
"longitude": 4.8882
},
{
"_links": {
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001236"
}
},
"_total": null,
"lcm_id": "LCM-001236",
"pole_number": "P-044",
"status": "ONLINE",
"position_in_section": 3,
"has_trigger": false,
"latitude": 52.3708,
"longitude": 4.8885
},
{
"_links": {
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001237"
}
},
"_total": null,
"lcm_id": "LCM-001237",
"pole_number": "P-045",
"status": "ONLINE",
"position_in_section": 4,
"has_trigger": false,
"latitude": 52.3711,
"longitude": 4.8888
},
{
"_links": {
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001238"
}
},
"_total": null,
"lcm_id": "LCM-001238",
"pole_number": "P-046",
"status": "OFFLINE",
"position_in_section": 5,
"has_trigger": false,
"latitude": 52.3714,
"longitude": 4.8891
}
]
},
"_total": 5,
"road_section_id": 50,
"name": "Keizersgracht North - Segment A",
"order": 1
}
Returns the devices within a specific road section of a motion configuration. Devices are returned in their position order, which determines the wave of light triggered by motion.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| motion_config_id | integer | The motion configuration identifier. |
| road_section_id | integer | The road section identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| road_section_id | integer | The road section identifier. |
| name | string | Name of the road section. |
| order | integer | Sequential order within the motion config. |
Embedded device fields are the same as described in /motion-configs/{motion_config_id}/devices.
Error Responses
| Status | Description |
|---|---|
| 404 | Motion config or road section not found. |
/motion-configs/{motion_config_id}/road-sections/{road_section_id}/invert-devices [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/motion-configs/12/road-sections/50/invert-devices" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/road-sections/50/invert-devices"
},
"road_section": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12/road-sections/50"
}
},
"_total": null,
"road_section_id": 50,
"name": "Keizersgracht North - Segment A",
"message": "Device order inverted successfully.",
"device_count": 5
}
Inverts (reverses) the order of devices within a road section. This effectively reverses the direction of the motion-triggered lighting wave. For example, if devices were ordered P-042 through P-046, after inversion they will be ordered P-046 through P-042.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| motion_config_id | integer | The motion configuration identifier. |
| road_section_id | integer | The road section identifier. |
Error Responses
| Status | Description |
|---|---|
| 404 | Motion config or road section not found. |
/motion-configs/gateways [GET]
Request
curl "https://httpapi.sustainder.com/v2/motion-configs/gateways?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/gateways?application_artkey=35"
}
},
"_embedded": {
"gateways": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289"
},
"motion_configs": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289/motion-configs"
}
},
"_total": null,
"gateway_id": "352890061121289",
"gateway_status": "ONLINE",
"timezone": "Europe/Amsterdam",
"motion_config_count": 3
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121345"
},
"motion_configs": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121345/motion-configs"
}
},
"_total": null,
"gateway_id": "352890061121345",
"gateway_status": "ONLINE",
"timezone": "Europe/Amsterdam",
"motion_config_count": 1
}
]
},
"_total": 2
}
Lists all gateways that have motion configurations associated with them for the given application.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| gateway_id | string | The unique gateway identifier (IMEI number). |
| gateway_status | string | Online status: ONLINE or OFFLINE. |
| timezone | string | The timezone of the gateway. |
| motion_config_count | integer | Number of motion configs associated with this gateway. |
/motion-configs/devices [GET]
Request
curl "https://httpapi.sustainder.com/v2/motion-configs/devices?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/devices?application_artkey=35"
}
},
"_embedded": {
"devices": [
{
"_links": {
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
},
"motion_config": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12"
}
},
"_total": null,
"lcm_id": "LCM-001234",
"pole_number": "P-042",
"status": "ONLINE",
"gateway_id": "352890061121289",
"motion_config_id": 12,
"motion_config_name": "Keizersgracht North",
"road_section_id": 50,
"road_section_name": "Keizersgracht North - Segment A",
"has_trigger": true
},
{
"_links": {
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001235"
},
"motion_config": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12"
}
},
"_total": null,
"lcm_id": "LCM-001235",
"pole_number": "P-043",
"status": "ONLINE",
"gateway_id": "352890061121289",
"motion_config_id": 12,
"motion_config_name": "Keizersgracht North",
"road_section_id": 50,
"road_section_name": "Keizersgracht North - Segment A",
"has_trigger": true
}
]
},
"_total": 32
}
Lists all devices across all motion configurations for the given application. This provides a flat view of every LCM participating in any motion config.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
| pole_number | string | The pole number where the device is mounted. |
| status | string | Online status: ONLINE or OFFLINE. |
| gateway_id | string | The gateway this device communicates through. |
| motion_config_id | integer | The motion config this device belongs to. |
| motion_config_name | string | Name of the motion config. |
| road_section_id | integer | The road section this device belongs to. |
| road_section_name | string | Name of the road section. |
| has_trigger | boolean | Whether this device has a motion trigger sensor. |
/gateways/{gateway_id}/motion-configs [GET]
Request
curl "https://httpapi.sustainder.com/v2/gateways/352890061121289/motion-configs" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289/motion-configs"
},
"gateway": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289"
}
},
"_embedded": {
"motion_configs": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/12"
}
},
"_total": null,
"motion_config_id": 12,
"name": "Keizersgracht North",
"description": "Motion lighting for the northern section of Keizersgracht",
"enabled": true,
"road_section_count": 4,
"device_count": 16,
"created_at": "2024-08-10T09:15:00Z",
"updated_at": "2025-01-20T14:30:00Z"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/18"
}
},
"_total": null,
"motion_config_id": 18,
"name": "Prinsengracht East",
"description": "Motion lighting along Prinsengracht eastern side",
"enabled": true,
"road_section_count": 3,
"device_count": 12,
"created_at": "2024-10-22T16:00:00Z",
"updated_at": "2025-03-01T10:15:00Z"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/21"
}
},
"_total": null,
"motion_config_id": 21,
"name": "Leidsestraat Pedestrian",
"description": "Motion-triggered lighting for the pedestrian zone",
"enabled": false,
"road_section_count": 2,
"device_count": 8,
"created_at": "2025-01-05T08:30:00Z",
"updated_at": "2025-02-28T13:45:00Z"
}
]
},
"_total": 3
}
Returns all motion configurations associated with a specific gateway.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| gateway_id | string | The gateway identifier (IMEI number). |
Error Responses
| Status | Description |
|---|---|
| 404 | Gateway not found. |
/gateways/{gateway_id}/devices [GET]
Request
curl "https://httpapi.sustainder.com/v2/gateways/352890061121289/devices" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289/devices"
},
"gateway": {
"href": "https://httpapi.sustainder.com/v2/gateways/352890061121289"
}
},
"_embedded": {
"devices": [
{
"_links": {
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
}
},
"_total": null,
"lcm_id": "LCM-001234",
"pole_number": "P-042",
"status": "ONLINE",
"motion_config_id": 12,
"motion_config_name": "Keizersgracht North",
"road_section_id": 50,
"has_trigger": true,
"latitude": 52.3702,
"longitude": 4.8879
},
{
"_links": {
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001235"
}
},
"_total": null,
"lcm_id": "LCM-001235",
"pole_number": "P-043",
"status": "ONLINE",
"motion_config_id": 12,
"motion_config_name": "Keizersgracht North",
"road_section_id": 50,
"has_trigger": true,
"latitude": 52.3705,
"longitude": 4.8882
},
{
"_links": {
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001240"
}
},
"_total": null,
"lcm_id": "LCM-001240",
"pole_number": "P-048",
"status": "ONLINE",
"motion_config_id": 12,
"motion_config_name": "Keizersgracht North",
"road_section_id": 51,
"has_trigger": false,
"latitude": 52.3720,
"longitude": 4.8900
}
]
},
"_total": 36
}
Returns all devices connected to a specific gateway that participate in motion configurations.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| gateway_id | string | The gateway identifier (IMEI number). |
Response Fields
| Field | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
| pole_number | string | The pole number where the device is mounted. |
| status | string | Online status: ONLINE or OFFLINE. |
| motion_config_id | integer | The motion config this device belongs to. |
| motion_config_name | string | Name of the motion config. |
| road_section_id | integer | The road section this device belongs to. |
| has_trigger | boolean | Whether this device has a motion trigger sensor. |
| latitude | float | Latitude coordinate of the device. |
| longitude | float | Longitude coordinate of the device. |
Error Responses
| Status | Description |
|---|---|
| 404 | Gateway not found. |
/motion-configs/data [GET]
Request
curl "https://httpapi.sustainder.com/v2/motion-configs/data?application_artkey=35&motion_config_id=12&from=2025-03-01T00:00:00Z&to=2025-03-22T23:59:59Z" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-configs/data?application_artkey=35&motion_config_id=12&from=2025-03-01T00:00:00Z&to=2025-03-22T23:59:59Z"
}
},
"_embedded": {
"motion_data": [
{
"_total": null,
"date": "2025-03-01",
"total_triggers": 482,
"average_triggers_per_hour": 20.1,
"peak_hour": "22:00",
"peak_hour_triggers": 58,
"unique_devices_triggered": 14,
"total_devices": 16
},
{
"_total": null,
"date": "2025-03-02",
"total_triggers": 523,
"average_triggers_per_hour": 21.8,
"peak_hour": "21:00",
"peak_hour_triggers": 63,
"unique_devices_triggered": 16,
"total_devices": 16
},
{
"_total": null,
"date": "2025-03-03",
"total_triggers": 391,
"average_triggers_per_hour": 16.3,
"peak_hour": "23:00",
"peak_hour_triggers": 45,
"unique_devices_triggered": 12,
"total_devices": 16
}
]
},
"_total": 3
}
Returns aggregated motion data and statistics for a motion configuration over a date range. Data is returned per day and includes trigger counts and peak usage information.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
| motion_config_id | integer | Yes | The motion configuration to retrieve data for. |
| from | string | Yes | Start of the date range (ISO 8601 format). |
| to | string | Yes | End of the date range (ISO 8601 format). |
Response Fields
| Field | Type | Description |
|---|---|---|
| date | string | The date for this data point (yyyy-MM-dd). |
| total_triggers | integer | Total number of motion triggers recorded on this date. |
| average_triggers_per_hour | float | Average number of triggers per hour (over the active night period). |
| peak_hour | string | The hour with the highest number of triggers (HH:mm). |
| peak_hour_triggers | integer | Number of triggers during the peak hour. |
| unique_devices_triggered | integer | Number of distinct devices that were triggered at least once. |
| total_devices | integer | Total number of devices in the motion config. |
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid date range or missing required parameters. |
| 404 | Motion config not found. |
/motion-data/heatmap [GET]
Request
curl "https://httpapi.sustainder.com/v2/motion-data/heatmap?application_artkey=35&motion_config_id=12&from=2025-03-15T00:00:00Z&to=2025-03-22T23:59:59Z" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-data/heatmap?application_artkey=35&motion_config_id=12&from=2025-03-15T00:00:00Z&to=2025-03-22T23:59:59Z"
}
},
"_embedded": {
"heatmap": [
{
"_total": null,
"hour": "18:00",
"monday": 12,
"tuesday": 15,
"wednesday": 14,
"thursday": 13,
"friday": 22,
"saturday": 28,
"sunday": 18
},
{
"_total": null,
"hour": "19:00",
"monday": 18,
"tuesday": 20,
"wednesday": 19,
"thursday": 17,
"friday": 31,
"saturday": 35,
"sunday": 24
},
{
"_total": null,
"hour": "20:00",
"monday": 25,
"tuesday": 28,
"wednesday": 26,
"thursday": 24,
"friday": 42,
"saturday": 48,
"sunday": 30
},
{
"_total": null,
"hour": "21:00",
"monday": 32,
"tuesday": 35,
"wednesday": 33,
"thursday": 30,
"friday": 55,
"saturday": 63,
"sunday": 38
},
{
"_total": null,
"hour": "22:00",
"monday": 28,
"tuesday": 30,
"wednesday": 29,
"thursday": 27,
"friday": 48,
"saturday": 58,
"sunday": 35
},
{
"_total": null,
"hour": "23:00",
"monday": 15,
"tuesday": 18,
"wednesday": 16,
"thursday": 14,
"friday": 35,
"saturday": 45,
"sunday": 22
}
]
},
"_total": 6
}
Returns motion heatmap data for a motion configuration. Data is aggregated by hour of day and day of week, showing the average number of motion triggers. This is useful for visualizing traffic patterns and identifying peak usage periods.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
| motion_config_id | integer | Yes | The motion configuration to retrieve heatmap data for. |
| from | string | Yes | Start of the date range (ISO 8601 format). |
| to | string | Yes | End of the date range (ISO 8601 format). |
Response Fields
| Field | Type | Description |
|---|---|---|
| hour | string | The hour of day (HH:mm). Only hours with activity are included. |
| monday | integer | Average trigger count for Monday during this hour. |
| tuesday | integer | Average trigger count for Tuesday during this hour. |
| wednesday | integer | Average trigger count for Wednesday during this hour. |
| thursday | integer | Average trigger count for Thursday during this hour. |
| friday | integer | Average trigger count for Friday during this hour. |
| saturday | integer | Average trigger count for Saturday during this hour. |
| sunday | integer | Average trigger count for Sunday during this hour. |
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid date range or missing required parameters. |
| 404 | Motion config not found. |
/motion-data/heatmap/devices [GET]
Request
curl "https://httpapi.sustainder.com/v2/motion-data/heatmap/devices?application_artkey=35&motion_config_id=12&from=2025-03-15T00:00:00Z&to=2025-03-22T23:59:59Z" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/motion-data/heatmap/devices?application_artkey=35&motion_config_id=12&from=2025-03-15T00:00:00Z&to=2025-03-22T23:59:59Z"
}
},
"_embedded": {
"device_heatmap": [
{
"_total": null,
"lcm_id": "LCM-001234",
"pole_number": "P-042",
"road_section_id": 50,
"road_section_name": "Keizersgracht North - Segment A",
"total_triggers": 1247,
"average_daily_triggers": 178.1,
"intensity": 0.92,
"latitude": 52.3702,
"longitude": 4.8879
},
{
"_total": null,
"lcm_id": "LCM-001235",
"pole_number": "P-043",
"road_section_id": 50,
"road_section_name": "Keizersgracht North - Segment A",
"total_triggers": 1183,
"average_daily_triggers": 169.0,
"intensity": 0.87,
"latitude": 52.3705,
"longitude": 4.8882
},
{
"_total": null,
"lcm_id": "LCM-001236",
"pole_number": "P-044",
"road_section_id": 50,
"road_section_name": "Keizersgracht North - Segment A",
"total_triggers": 985,
"average_daily_triggers": 140.7,
"intensity": 0.73,
"latitude": 52.3708,
"longitude": 4.8885
},
{
"_total": null,
"lcm_id": "LCM-001240",
"pole_number": "P-048",
"road_section_id": 51,
"road_section_name": "Keizersgracht North - Segment B",
"total_triggers": 642,
"average_daily_triggers": 91.7,
"intensity": 0.47,
"latitude": 52.3720,
"longitude": 4.8900
}
]
},
"_total": 16
}
Returns per-device motion intensity data for a motion configuration. Each device is scored with an intensity value (0.0 to 1.0) representing how frequently it was triggered relative to the most active device. This data is suitable for rendering geographic heatmaps.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
| motion_config_id | integer | Yes | The motion configuration to retrieve device heatmap data for. |
| from | string | Yes | Start of the date range (ISO 8601 format). |
| to | string | Yes | End of the date range (ISO 8601 format). |
Response Fields
| Field | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
| pole_number | string | The pole number where the device is mounted. |
| road_section_id | integer | The road section this device belongs to. |
| road_section_name | string | Name of the road section. |
| total_triggers | integer | Total number of times this device was triggered in the date range. |
| average_daily_triggers | float | Average number of triggers per day. |
| intensity | float | Normalized intensity score (0.0 to 1.0). A value of 1.0 represents the device with the most triggers; other devices are scaled relative to this maximum. |
| latitude | float | Latitude coordinate of the device. |
| longitude | float | Longitude coordinate of the device. |
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid date range or missing required parameters. |
| 404 | Motion config not found. |
Layers
Layers are configurable data overlays that can be applied to the map and dashboard views. Each layer represents a visual or functional data set — such as energy consumption heatmaps, error status overlays, dimming scheme visualizations, or custom data integrations. Layers can be enabled or disabled independently, allowing users to compose the view that best fits their operational needs.
/layers [GET]
Request
curl "https://httpapi.sustainder.com/v2/layers?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/layers?application_artkey=35"
}
},
"_embedded": {
"layers": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/layers/1"
},
"status": {
"href": "https://httpapi.sustainder.com/v2/layers/1/status"
}
},
"_total": null,
"layer_id": 1,
"name": "Energy Consumption",
"description": "Heatmap overlay showing energy consumption per node in kWh",
"type": "heatmap",
"enabled": true,
"created_on": "2024-06-15T10:30:00Z",
"updated_on": "2024-11-10T08:45:12Z"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/layers/2"
},
"status": {
"href": "https://httpapi.sustainder.com/v2/layers/2/status"
}
},
"_total": null,
"layer_id": 2,
"name": "Error Status",
"description": "Overlay indicating nodes with active errors",
"type": "status",
"enabled": true,
"created_on": "2024-06-15T10:35:00Z",
"updated_on": "2024-11-14T22:16:00Z"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/layers/3"
},
"status": {
"href": "https://httpapi.sustainder.com/v2/layers/3/status"
}
},
"_total": null,
"layer_id": 3,
"name": "Dimming Scheme Zones",
"description": "Color-coded zones showing which dimming scheme is active per area",
"type": "zone",
"enabled": false,
"created_on": "2024-08-22T14:00:00Z",
"updated_on": "2024-10-01T09:12:33Z"
}
]
},
"_total": 3
}
Lists all layers configured for the specified application.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| layer_id | integer | Unique layer identifier. |
| name | string | Display name of the layer. |
| description | string | Human-readable description of what the layer shows. |
| type | string | The layer type. See Layer Types below. |
| enabled | boolean | Whether the layer is currently enabled. |
| created_on | string | ISO 8601 timestamp of when the layer was created. |
| updated_on | string | ISO 8601 timestamp of the last modification. |
Layer Types
| Type | Description |
|---|---|
heatmap |
Gradient-based overlay visualizing intensity of a metric (e.g., energy, power). |
status |
Categorical overlay using icons or colors to indicate device states (e.g., errors, online/offline). |
zone |
Area-based overlay grouping nodes by a shared attribute (e.g., dimming scheme, group). |
custom |
User-defined layer with custom data source and rendering configuration. |
/layers [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/layers" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Power Usage",
"description": "Real-time power consumption overlay in Watts",
"type": "heatmap",
"enabled": true,
"configuration": {
"metric": "power",
"unit": "W",
"color_scale": "green_to_red",
"min_value": 0,
"max_value": 100
},
"application_artkey": 35
}'
Request body
{
"name": "Power Usage",
"description": "Real-time power consumption overlay in Watts",
"type": "heatmap",
"enabled": true,
"configuration": {
"metric": "power",
"unit": "W",
"color_scale": "green_to_red",
"min_value": 0,
"max_value": 100
},
"application_artkey": 35
}
Response (201 Created)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/layers/4"
},
"status": {
"href": "https://httpapi.sustainder.com/v2/layers/4/status"
}
},
"layer_id": 4,
"name": "Power Usage",
"description": "Real-time power consumption overlay in Watts",
"type": "heatmap",
"enabled": true,
"configuration": {
"metric": "power",
"unit": "W",
"color_scale": "green_to_red",
"min_value": 0,
"max_value": 100
},
"created_on": "2024-11-15T16:00:00Z",
"updated_on": "2024-11-15T16:00:00Z"
}
Creates a new layer for the specified application. The layer can be created in an enabled or disabled state.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Display name for the layer. Must be unique within the application. |
| description | string | No | Human-readable description of the layer. |
| type | string | Yes | Layer type: heatmap, status, zone, or custom. |
| enabled | boolean | No | Whether the layer should be enabled on creation. Default: false. |
| configuration | object | Yes | Layer-specific configuration. Structure depends on the layer type. |
| application_artkey | integer | Yes | The application identifier. |
Configuration Object (Heatmap)
| Field | Type | Required | Description |
|---|---|---|---|
| metric | string | Yes | Sensor metric to visualize (e.g., power, energy, temperature). |
| unit | string | No | Display unit label. |
| color_scale | string | No | Color scale preset: green_to_red, blue_to_red, grayscale. Default: green_to_red. |
| min_value | number | No | Minimum value for the scale. |
| max_value | number | No | Maximum value for the scale. |
Configuration Object (Status)
| Field | Type | Required | Description |
|---|---|---|---|
| statuses | array | Yes | List of status values to display (e.g., ["ONLINE", "OFFLINE"]). |
| show_errors | boolean | No | Whether to include error indicators. Default: true. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., missing required fields, invalid type, duplicate name). |
| 409 | A layer with the same name already exists in this application. |
/layers/{layer_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/layers/1" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/layers/1"
},
"layers": {
"href": "https://httpapi.sustainder.com/v2/layers"
},
"status": {
"href": "https://httpapi.sustainder.com/v2/layers/1/status"
}
},
"_total": null,
"layer_id": 1,
"name": "Energy Consumption",
"description": "Heatmap overlay showing energy consumption per node in kWh",
"type": "heatmap",
"enabled": true,
"configuration": {
"metric": "energy",
"unit": "kWh",
"color_scale": "green_to_red",
"min_value": 0,
"max_value": 5000
},
"created_on": "2024-06-15T10:30:00Z",
"updated_on": "2024-11-10T08:45:12Z"
}
Returns the full details and configuration for a specific layer.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| layer_id | integer | The unique layer identifier. |
Response Fields
Same fields as the layer list response, plus the full configuration object.
Error Responses
| Status | Description |
|---|---|
| 404 | Layer not found. |
/layers/{layer_id}/status [GET]
Request
curl "https://httpapi.sustainder.com/v2/layers/1/status" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/layers/1/status"
},
"layer": {
"href": "https://httpapi.sustainder.com/v2/layers/1"
}
},
"layer_id": 1,
"name": "Energy Consumption",
"enabled": true
}
Returns the current enabled/disabled status of a layer.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| layer_id | integer | The unique layer identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| layer_id | integer | The layer identifier. |
| name | string | Display name of the layer. |
| enabled | boolean | Whether the layer is currently enabled. |
Error Responses
| Status | Description |
|---|---|
| 404 | Layer not found. |
/layers/{layer_id}/status [POST]
Request — Enable a layer
curl -X POST "https://httpapi.sustainder.com/v2/layers/3/status" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"enabled": true
}'
Request body
{
"enabled": true
}
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/layers/3/status"
},
"layer": {
"href": "https://httpapi.sustainder.com/v2/layers/3"
}
},
"layer_id": 3,
"name": "Dimming Scheme Zones",
"enabled": true
}
Request — Disable a layer
curl -X POST "https://httpapi.sustainder.com/v2/layers/1/status" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"enabled": false
}'
Request body
{
"enabled": false
}
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/layers/1/status"
},
"layer": {
"href": "https://httpapi.sustainder.com/v2/layers/1"
}
},
"layer_id": 1,
"name": "Energy Consumption",
"enabled": false
}
Enables or disables a layer. Toggling a layer's status controls whether it is rendered in the map and dashboard views.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| layer_id | integer | The unique layer identifier. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| enabled | boolean | Yes | Set to true to enable the layer, false to disable it. |
Error Responses
| Status | Description |
|---|---|
| 400 | Missing or invalid enabled parameter. |
| 404 | Layer not found. |
/layers/validate [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/layers/validate" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Temperature Monitor",
"description": "Overlay showing internal LCM temperatures",
"type": "heatmap",
"configuration": {
"metric": "temperature",
"unit": "°C",
"color_scale": "blue_to_red",
"min_value": -10,
"max_value": 60
},
"application_artkey": 35
}'
Request body
{
"name": "Temperature Monitor",
"description": "Overlay showing internal LCM temperatures",
"type": "heatmap",
"configuration": {
"metric": "temperature",
"unit": "\u00b0C",
"color_scale": "blue_to_red",
"min_value": -10,
"max_value": 60
},
"application_artkey": 35
}
Response — Valid (200 OK)
{
"valid": true,
"errors": []
}
Request — Invalid configuration
curl -X POST "https://httpapi.sustainder.com/v2/layers/validate" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "",
"type": "invalid_type",
"configuration": {},
"application_artkey": 35
}'
Request body
{
"name": "",
"type": "invalid_type",
"configuration": {},
"application_artkey": 35
}
Response — Invalid (200 OK)
{
"valid": false,
"errors": [
{
"field": "name",
"message": "Name is required and cannot be empty."
},
{
"field": "type",
"message": "Invalid layer type. Must be one of: heatmap, status, zone, custom."
},
{
"field": "configuration.metric",
"message": "The metric field is required for heatmap layers."
}
]
}
Validates a layer configuration without creating it. Use this endpoint to check for errors before submitting a create layer request. The endpoint always returns a 200 OK status with a valid boolean and an array of validation errors.
Request Parameters
Same as POST /layers. All fields are validated as if creating a new layer.
Response Fields
| Field | Type | Description |
|---|---|---|
| valid | boolean | true if the configuration is valid, false otherwise. |
| errors | array | List of validation error objects. Empty if valid is true. |
| errors[].field | string | The field that failed validation (dot notation for nested fields). |
| errors[].message | string | Human-readable description of the validation error. |
Custom Fields
Custom fields allow you to define additional metadata fields for devices within an application.
/custom-fields [GET]
Request
curl "https://httpapi.sustainder.com/v2/custom-fields?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"custom_fields": [
{
"artkey": 1,
"name": "maintenance_contract",
"label": "Maintenance Contract",
"is_required": false,
"default_value": "Standard"
},
{
"artkey": 2,
"name": "installation_date",
"label": "Installation Date",
"is_required": true,
"default_value": null
}
]
}
Lists all custom field definitions for the application.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| artkey | integer | Unique field definition identifier. |
| name | string | Machine-readable field name (unique per application). |
| label | string | Human-readable display label. |
| is_required | boolean | Whether a value is required for all devices. |
| default_value | string\ | null |
/custom-fields [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/custom-fields" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "circuit_number",
"label": "Circuit Number",
"is_required": false,
"default_value": null,
"application_artkey": 35
}'
Request body
{
"name": "circuit_number",
"label": "Circuit Number",
"is_required": false,
"default_value": null,
"application_artkey": 35
}
Response (201 Created)
{
"artkey": 3,
"name": "circuit_number",
"label": "Circuit Number",
"is_required": false,
"default_value": null
}
Creates a new custom field definition for the application.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | Yes | Machine-readable name (must be unique per application). |
| label | string | Yes | Human-readable display label. |
| is_required | boolean | No | Whether the field is required. Default: false. |
| default_value | string | No | Default value for new devices. |
| application_artkey | integer | Yes | The application identifier. |
Error Responses
| Status | Description |
|---|---|
| 400 | Field name already exists for this application. |
/custom-fields/{field_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/custom-fields/1?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"artkey": 1,
"name": "maintenance_contract",
"label": "Maintenance Contract",
"is_required": false,
"default_value": "Standard"
}
Returns a specific custom field definition.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| field_id | integer | The custom field artkey. |
Error Responses
| Status | Description |
|---|---|
| 404 | Custom field not found. |
/custom-fields/{field_id} [PATCH]
Request
curl -X PATCH "https://httpapi.sustainder.com/v2/custom-fields/1" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"label": "Service Contract",
"is_required": true
}'
Request body
{
"label": "Service Contract",
"is_required": true
}
Response (200 OK)
{
"artkey": 1,
"name": "maintenance_contract",
"label": "Service Contract",
"is_required": true,
"default_value": "Standard"
}
Updates a custom field definition. Only provided fields are updated.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| field_id | integer | The custom field artkey. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | No | New machine-readable name. |
| label | string | No | New display label. |
| is_required | boolean | No | New required status. |
| default_value | string | No | New default value. |
/custom-fields/{field_id} [DELETE]
Request
curl -X DELETE "https://httpapi.sustainder.com/v2/custom-fields/1" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"message": "Custom field deleted successfully."
}
Deletes a custom field definition and all associated values across all devices.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| field_id | integer | The custom field artkey. |
Buildings
Buildings represent indoor lighting installations managed through the Sustainder platform. Each building contains one or more floors, and each floor can have spaces (rooms, hallways, zones), lamp channels, controllers, and triggers (sensors that activate lighting behaviors). This hierarchical structure allows fine-grained control over indoor lighting scenarios.
/buildings/locations [GET]
Request
curl "https://httpapi.sustainder.com/v2/buildings/locations?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/locations?application_artkey=35"
}
},
"_embedded": {
"buildings": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001"
}
},
"_total": null,
"building_artkey": 1001,
"name": "Rijksmuseum Parking Garage",
"address": "Museumstraat 1",
"city": "Amsterdam",
"country": "NL",
"latitude": 52.3600,
"longitude": 4.8852,
"floor_count": 4,
"total_lamps": 320,
"status": "ACTIVE"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1002"
}
},
"_total": null,
"building_artkey": 1002,
"name": "Zuidas Office Tower B",
"address": "Gustav Mahlerlaan 10",
"city": "Amsterdam",
"country": "NL",
"latitude": 52.3380,
"longitude": 4.8740,
"floor_count": 12,
"total_lamps": 1450,
"status": "ACTIVE"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1003"
}
},
"_total": null,
"building_artkey": 1003,
"name": "Schiphol Cargo Warehouse 7",
"address": "Anchoragelaan 48",
"city": "Schiphol",
"country": "NL",
"latitude": 52.3105,
"longitude": 4.7683,
"floor_count": 1,
"total_lamps": 580,
"status": "ACTIVE"
}
]
},
"_total": 3
}
Lists all building locations for a given application. This provides a top-level overview of all buildings under management.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| building_artkey | integer | Unique identifier for the building. |
| name | string | Human-readable name of the building. |
| address | string | Street address of the building. |
| city | string | City where the building is located. |
| country | string | Country code (ISO 3166-1 alpha-2). |
| latitude | float | Latitude coordinate of the building. |
| longitude | float | Longitude coordinate of the building. |
| floor_count | integer | Number of floors in the building. |
| total_lamps | integer | Total number of lamp channels across all floors. |
| status | string | Building status: ACTIVE or INACTIVE. |
/buildings/{building_artkey} [GET]
Request
curl "https://httpapi.sustainder.com/v2/buildings/1001" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001"
},
"buildings": {
"href": "https://httpapi.sustainder.com/v2/buildings/locations"
}
},
"_embedded": {
"floors": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001"
}
},
"_total": null,
"floor_artkey": 2001,
"name": "Level -3",
"floor_number": -3,
"space_count": 6,
"lamp_count": 85,
"controller_count": 4,
"trigger_count": 12
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2002"
}
},
"_total": null,
"floor_artkey": 2002,
"name": "Level -2",
"floor_number": -2,
"space_count": 6,
"lamp_count": 80,
"controller_count": 4,
"trigger_count": 12
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2003"
}
},
"_total": null,
"floor_artkey": 2003,
"name": "Level -1",
"floor_number": -1,
"space_count": 8,
"lamp_count": 90,
"controller_count": 5,
"trigger_count": 14
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2004"
}
},
"_total": null,
"floor_artkey": 2004,
"name": "Ground Level",
"floor_number": 0,
"space_count": 5,
"lamp_count": 65,
"controller_count": 3,
"trigger_count": 10
}
]
},
"_total": null,
"building_artkey": 1001,
"name": "Rijksmuseum Parking Garage",
"address": "Museumstraat 1",
"city": "Amsterdam",
"country": "NL",
"latitude": 52.3600,
"longitude": 4.8852,
"floor_count": 4,
"total_lamps": 320,
"status": "ACTIVE",
"created_at": "2024-03-15T10:00:00Z",
"updated_at": "2025-02-20T16:45:00Z"
}
Returns detailed information for a specific building, including its embedded floor listing.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| building_artkey | integer | The building identifier. |
Response Fields
Building-level fields are the same as in /buildings/locations, with these additional fields:
| Field | Type | Description |
|---|---|---|
| created_at | string | ISO 8601 timestamp of when the building was created. |
| updated_at | string | ISO 8601 timestamp of when the building was last modified. |
Embedded Floor Fields
| Field | Type | Description |
|---|---|---|
| floor_artkey | integer | Unique identifier for the floor. |
| name | string | Human-readable name of the floor. |
| floor_number | integer | Numeric floor level (negative for underground levels, 0 for ground). |
| space_count | integer | Number of spaces (rooms/zones) on this floor. |
| lamp_count | integer | Number of lamp channels on this floor. |
| controller_count | integer | Number of controllers on this floor. |
| trigger_count | integer | Number of triggers (motion sensors, switches) on this floor. |
Error Responses
| Status | Description |
|---|---|
| 404 | Building not found. |
/buildings/{building_artkey}/floors/{floor_artkey} [GET]
Request
curl "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001"
},
"building": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001"
},
"controllers": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/controllers"
},
"channels": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/lamps/channels"
}
},
"_embedded": {
"spaces": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/spaces/3001"
}
},
"_total": null,
"space_artkey": 3001,
"name": "Parking Zone A",
"space_type": "parking",
"lamp_count": 18,
"trigger_count": 3
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/spaces/3002"
}
},
"_total": null,
"space_artkey": 3002,
"name": "Parking Zone B",
"space_type": "parking",
"lamp_count": 16,
"trigger_count": 3
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/spaces/3003"
}
},
"_total": null,
"space_artkey": 3003,
"name": "Driving Lane",
"space_type": "corridor",
"lamp_count": 22,
"trigger_count": 4
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/spaces/3004"
}
},
"_total": null,
"space_artkey": 3004,
"name": "Stairwell East",
"space_type": "stairwell",
"lamp_count": 6,
"trigger_count": 1
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/spaces/3005"
}
},
"_total": null,
"space_artkey": 3005,
"name": "Stairwell West",
"space_type": "stairwell",
"lamp_count": 6,
"trigger_count": 1
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/spaces/3006"
}
},
"_total": null,
"space_artkey": 3006,
"name": "Elevator Lobby",
"space_type": "lobby",
"lamp_count": 17,
"trigger_count": 0
}
],
"triggers": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/triggers/4001"
}
},
"_total": null,
"building_trigger_artkey": 4001,
"name": "Zone A Entry Sensor",
"trigger_type": "PIR",
"space_artkey": 3001,
"space_name": "Parking Zone A",
"enabled": true
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/triggers/4002"
}
},
"_total": null,
"building_trigger_artkey": 4002,
"name": "Zone A Mid Sensor",
"trigger_type": "PIR",
"space_artkey": 3001,
"space_name": "Parking Zone A",
"enabled": true
}
]
},
"_total": null,
"floor_artkey": 2001,
"name": "Level -3",
"floor_number": -3,
"space_count": 6,
"lamp_count": 85,
"controller_count": 4,
"trigger_count": 12
}
Returns detailed information for a specific floor, including embedded spaces and triggers.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| building_artkey | integer | The building identifier. |
| floor_artkey | integer | The floor identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| floor_artkey | integer | Unique identifier for the floor. |
| name | string | Human-readable name of the floor. |
| floor_number | integer | Numeric floor level. |
| space_count | integer | Number of spaces on this floor. |
| lamp_count | integer | Number of lamp channels on this floor. |
| controller_count | integer | Number of controllers managing this floor. |
| trigger_count | integer | Number of triggers on this floor. |
Embedded Space Fields
| Field | Type | Description |
|---|---|---|
| space_artkey | integer | Unique identifier for the space. |
| name | string | Human-readable name of the space. |
| space_type | string | Type of space: parking, corridor, stairwell, lobby, office, warehouse, or other. |
| lamp_count | integer | Number of lamp channels in this space. |
| trigger_count | integer | Number of triggers in this space. |
Error Responses
| Status | Description |
|---|---|
| 404 | Building or floor not found. |
/buildings/{building_artkey}/floors/{floor_artkey}/controllers [GET]
Request
curl "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/controllers" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/controllers"
},
"floor": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001"
}
},
"_embedded": {
"controllers": [
{
"_total": null,
"controller_id": "CTRL-P001",
"name": "Panel 1 - Zone A",
"status": "ONLINE",
"model": "dali-gateway-64",
"firmware_version": "3.2.1",
"dali_bus_count": 2,
"connected_lamps": 32,
"last_communication": "2025-03-23T08:15:22Z",
"ip_address": "10.0.1.101"
},
{
"_total": null,
"controller_id": "CTRL-P002",
"name": "Panel 2 - Zone B",
"status": "ONLINE",
"model": "dali-gateway-64",
"firmware_version": "3.2.1",
"dali_bus_count": 2,
"connected_lamps": 28,
"last_communication": "2025-03-23T08:15:18Z",
"ip_address": "10.0.1.102"
},
{
"_total": null,
"controller_id": "CTRL-P003",
"name": "Panel 3 - Lane",
"status": "ONLINE",
"model": "dali-gateway-32",
"firmware_version": "3.1.8",
"dali_bus_count": 1,
"connected_lamps": 22,
"last_communication": "2025-03-23T08:14:55Z",
"ip_address": "10.0.1.103"
},
{
"_total": null,
"controller_id": "CTRL-P004",
"name": "Panel 4 - Stairwells",
"status": "OFFLINE",
"model": "dali-gateway-16",
"firmware_version": "3.1.8",
"dali_bus_count": 1,
"connected_lamps": 3,
"last_communication": "2025-03-22T22:30:00Z",
"ip_address": "10.0.1.104"
}
]
},
"_total": 4
}
Returns the controllers (DALI gateways or similar hardware) that manage the lighting on a specific floor.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| building_artkey | integer | The building identifier. |
| floor_artkey | integer | The floor identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| controller_id | string | Unique identifier for the controller. |
| name | string | Human-readable name of the controller. |
| status | string | Online status: ONLINE or OFFLINE. |
| model | string | Controller model (e.g., dali-gateway-64, dali-gateway-32). |
| firmware_version | string | Firmware version running on the controller. |
| dali_bus_count | integer | Number of DALI buses available on this controller. |
| connected_lamps | integer | Number of lamp channels connected to this controller. |
| last_communication | string | ISO 8601 timestamp of the last communication from the controller. |
| ip_address | string | IP address of the controller on the local network. |
Error Responses
| Status | Description |
|---|---|
| 404 | Building or floor not found. |
/buildings/{building_artkey}/floors/{floor_artkey}/spaces/{space_artkey} [GET]
Request
curl "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/spaces/3001" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/spaces/3001"
},
"floor": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001"
}
},
"_embedded": {
"lamps": [
{
"_total": null,
"lamp_channel_id": "LAMP-A001-CH1",
"name": "Bay 1 - Left",
"channel": 1,
"controller_id": "CTRL-P001",
"dali_address": 0,
"current_level": 30,
"max_level": 100,
"status": "ON",
"wattage": 36,
"lamp_type": "LED Panel"
},
{
"_total": null,
"lamp_channel_id": "LAMP-A001-CH2",
"name": "Bay 1 - Right",
"channel": 2,
"controller_id": "CTRL-P001",
"dali_address": 1,
"current_level": 30,
"max_level": 100,
"status": "ON",
"wattage": 36,
"lamp_type": "LED Panel"
},
{
"_total": null,
"lamp_channel_id": "LAMP-A002-CH1",
"name": "Bay 2 - Left",
"channel": 1,
"controller_id": "CTRL-P001",
"dali_address": 2,
"current_level": 0,
"max_level": 100,
"status": "OFF",
"wattage": 36,
"lamp_type": "LED Panel"
}
],
"triggers": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/triggers/4001"
}
},
"_total": null,
"building_trigger_artkey": 4001,
"name": "Zone A Entry Sensor",
"trigger_type": "PIR",
"enabled": true,
"last_triggered": "2025-03-23T07:42:15Z"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/triggers/4002"
}
},
"_total": null,
"building_trigger_artkey": 4002,
"name": "Zone A Mid Sensor",
"trigger_type": "PIR",
"enabled": true,
"last_triggered": "2025-03-23T07:38:50Z"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/triggers/4003"
}
},
"_total": null,
"building_trigger_artkey": 4003,
"name": "Zone A Exit Sensor",
"trigger_type": "PIR",
"enabled": true,
"last_triggered": "2025-03-23T07:45:02Z"
}
]
},
"_total": null,
"space_artkey": 3001,
"name": "Parking Zone A",
"space_type": "parking",
"lamp_count": 18,
"trigger_count": 3,
"average_light_level": 30,
"active_scene": "Idle"
}
Returns detailed information for a specific space, including its embedded lamps and triggers.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| building_artkey | integer | The building identifier. |
| floor_artkey | integer | The floor identifier. |
| space_artkey | integer | The space identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| space_artkey | integer | Unique identifier for the space. |
| name | string | Human-readable name of the space. |
| space_type | string | Type of space: parking, corridor, stairwell, lobby, office, warehouse, or other. |
| lamp_count | integer | Number of lamp channels in this space. |
| trigger_count | integer | Number of triggers in this space. |
| average_light_level | integer | Current average light output (0-100) across all lamps in this space. |
| active_scene | string | Name of the currently active lighting scene (e.g., Idle, Occupied, Emergency, Cleaning). |
Embedded Lamp Fields
| Field | Type | Description |
|---|---|---|
| lamp_channel_id | string | Unique identifier for the lamp channel. |
| name | string | Human-readable name of the lamp. |
| channel | integer | Channel number on the controller. |
| controller_id | string | The controller managing this lamp. |
| dali_address | integer | DALI short address (0-63). |
| current_level | integer | Current light output level (0-100). |
| max_level | integer | Maximum configured light level (0-100). |
| status | string | Lamp status: ON, OFF, or FAULT. |
| wattage | integer | Rated wattage of the lamp. |
| lamp_type | string | Type of lamp fixture (e.g., LED Panel, LED Downlight, LED Tube). |
Error Responses
| Status | Description |
|---|---|
| 404 | Building, floor, or space not found. |
/buildings/{building_artkey}/floors/{floor_artkey}/lamps/channels [GET]
Request
curl "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/lamps/channels" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/lamps/channels"
},
"floor": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001"
}
},
"_embedded": {
"channels": [
{
"_total": null,
"lamp_channel_id": "LAMP-A001-CH1",
"name": "Bay 1 - Left",
"channel": 1,
"controller_id": "CTRL-P001",
"dali_address": 0,
"space_artkey": 3001,
"space_name": "Parking Zone A",
"current_level": 30,
"max_level": 100,
"status": "ON",
"wattage": 36,
"lamp_type": "LED Panel",
"color_temperature": 4000,
"running_hours": 8520
},
{
"_total": null,
"lamp_channel_id": "LAMP-A001-CH2",
"name": "Bay 1 - Right",
"channel": 2,
"controller_id": "CTRL-P001",
"dali_address": 1,
"space_artkey": 3001,
"space_name": "Parking Zone A",
"current_level": 30,
"max_level": 100,
"status": "ON",
"wattage": 36,
"lamp_type": "LED Panel",
"color_temperature": 4000,
"running_hours": 8520
},
{
"_total": null,
"lamp_channel_id": "LAMP-C001-CH1",
"name": "Lane Entry",
"channel": 1,
"controller_id": "CTRL-P003",
"dali_address": 0,
"space_artkey": 3003,
"space_name": "Driving Lane",
"current_level": 80,
"max_level": 100,
"status": "ON",
"wattage": 54,
"lamp_type": "LED Tube",
"color_temperature": 4000,
"running_hours": 9200
},
{
"_total": null,
"lamp_channel_id": "LAMP-S001-CH1",
"name": "Stairwell East - Level 1",
"channel": 1,
"controller_id": "CTRL-P004",
"dali_address": 0,
"space_artkey": 3004,
"space_name": "Stairwell East",
"current_level": 0,
"max_level": 100,
"status": "OFF",
"wattage": 18,
"lamp_type": "LED Downlight",
"color_temperature": 3000,
"running_hours": 4100
}
]
},
"_total": 85
}
Returns all lamp channels on a specific floor, across all spaces. This provides a flat listing of every addressable lamp channel with its current state and configuration.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| building_artkey | integer | The building identifier. |
| floor_artkey | integer | The floor identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| lamp_channel_id | string | Unique identifier for the lamp channel. |
| name | string | Human-readable name of the lamp. |
| channel | integer | Channel number on the controller. |
| controller_id | string | The controller managing this lamp. |
| dali_address | integer | DALI short address (0-63). |
| space_artkey | integer | The space this lamp belongs to. |
| space_name | string | Name of the space. |
| current_level | integer | Current light output level (0-100). |
| max_level | integer | Maximum configured light level (0-100). |
| status | string | Lamp status: ON, OFF, or FAULT. |
| wattage | integer | Rated wattage of the lamp. |
| lamp_type | string | Type of lamp fixture. |
| color_temperature | integer | Color temperature in Kelvin. |
| running_hours | integer | Total running hours of the lamp. |
Error Responses
| Status | Description |
|---|---|
| 404 | Building or floor not found. |
/buildings/{building_artkey}/floors/{floor_artkey}/triggers/{building_trigger_artkey} [GET]
Request
curl "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/triggers/4001" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/triggers/4001"
},
"floor": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001"
},
"space": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/spaces/3001"
},
"configuration": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/triggers/4001/configuration"
}
},
"_total": null,
"building_trigger_artkey": 4001,
"name": "Zone A Entry Sensor",
"trigger_type": "PIR",
"space_artkey": 3001,
"space_name": "Parking Zone A",
"controller_id": "CTRL-P001",
"dali_address": 50,
"enabled": true,
"status": "ACTIVE",
"last_triggered": "2025-03-23T07:42:15Z",
"total_triggers_today": 87,
"firmware_version": "1.4.2"
}
Returns detailed information for a specific trigger (sensor) on a floor.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| building_artkey | integer | The building identifier. |
| floor_artkey | integer | The floor identifier. |
| building_trigger_artkey | integer | The trigger identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| building_trigger_artkey | integer | Unique identifier for the trigger. |
| name | string | Human-readable name of the trigger. |
| trigger_type | string | Type of trigger: PIR (passive infrared), RADAR, SWITCH, or DAYLIGHT. |
| space_artkey | integer | The space this trigger is associated with. |
| space_name | string | Name of the associated space. |
| controller_id | string | The controller this trigger is connected to. |
| dali_address | integer | DALI address of the trigger device. |
| enabled | boolean | Whether the trigger is currently active. |
| status | string | Current status: ACTIVE, IDLE, or FAULT. |
| last_triggered | string | ISO 8601 timestamp of the last time this trigger was activated. |
| total_triggers_today | integer | Number of trigger events recorded since midnight. |
| firmware_version | string | Firmware version of the trigger device. |
Error Responses
| Status | Description |
|---|---|
| 404 | Building, floor, or trigger not found. |
/buildings/{building_artkey}/floors/{floor_artkey}/triggers/{building_trigger_artkey}/configuration [GET]
Request
curl "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/triggers/4001/configuration" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/triggers/4001/configuration"
},
"trigger": {
"href": "https://httpapi.sustainder.com/v2/buildings/1001/floors/2001/triggers/4001"
}
},
"_total": null,
"building_trigger_artkey": 4001,
"name": "Zone A Entry Sensor",
"trigger_type": "PIR",
"sensitivity": 80,
"hold_time_seconds": 300,
"dead_time_seconds": 5,
"detection_range_meters": 12,
"triggered_light_level": 100,
"idle_light_level": 30,
"fade_in_seconds": 2,
"fade_out_seconds": 15,
"daylight_threshold_lux": null,
"linked_lamp_channels": [
"LAMP-A001-CH1",
"LAMP-A001-CH2",
"LAMP-A002-CH1",
"LAMP-A002-CH2",
"LAMP-A003-CH1",
"LAMP-A003-CH2"
],
"schedule": {
"enabled": true,
"active_from": "00:00",
"active_until": "23:59",
"active_days": ["mo", "tu", "we", "th", "fr", "sa", "su"]
}
}
Returns the configuration details for a specific trigger, including sensitivity settings, linked lamp channels, and scheduling.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| building_artkey | integer | The building identifier. |
| floor_artkey | integer | The floor identifier. |
| building_trigger_artkey | integer | The trigger identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| building_trigger_artkey | integer | Unique identifier for the trigger. |
| name | string | Human-readable name of the trigger. |
| trigger_type | string | Type of trigger: PIR, RADAR, SWITCH, or DAYLIGHT. |
| sensitivity | integer | Detection sensitivity (0-100). Higher values mean more sensitive. |
| hold_time_seconds | integer | Time in seconds that the triggered light level is maintained after the last motion detection. |
| dead_time_seconds | integer | Minimum time in seconds between successive trigger events. Prevents rapid toggling. |
| detection_range_meters | integer | Detection range in meters. |
| triggered_light_level | integer | Light level (0-100) to set when the trigger is activated. |
| idle_light_level | integer | Light level (0-100) when no trigger activity is detected. |
| fade_in_seconds | integer | Time in seconds for lamps to ramp up to the triggered level. |
| fade_out_seconds | integer | Time in seconds for lamps to ramp down to the idle level after the hold time expires. |
| daylight_threshold_lux | integer\ | null |
| linked_lamp_channels | array | List of lamp channel IDs that this trigger controls. |
| schedule | object | Schedule defining when the trigger is active. |
| schedule.enabled | boolean | Whether the schedule is active. |
| schedule.active_from | string | Start time in HH:mm format. |
| schedule.active_until | string | End time in HH:mm format. |
| schedule.active_days | array | Days of the week when the trigger is active: mo, tu, we, th, fr, sa, su. |
Error Responses
| Status | Description |
|---|---|
| 404 | Building, floor, or trigger not found. |
Cameras
Camera endpoints provide access to cameras attached to nodes and LCMs in the field. Cameras are typically mounted on streetlight poles alongside luminaires and can capture images for monitoring, incident review, or traffic analysis. Each camera is associated with a specific LCM that acts as its controller, providing power and network connectivity.
/cameras [GET]
Request
curl "https://httpapi.sustainder.com/v2/cameras?application_artkey=35&page=1" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/cameras?application_artkey=35&page=1"
},
"next": {
"href": "https://httpapi.sustainder.com/v2/cameras?application_artkey=35&page=2"
}
},
"_embedded": {
"cameras": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/cameras/501"
},
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
}
},
"_total": null,
"camera_id": 501,
"name": "Keizersgracht 42 - North",
"lcm_id": "LCM-001234",
"pole_number": "P-042",
"status": "ONLINE",
"model": "Axis P1375",
"resolution": "1920x1080",
"latitude": 52.3702,
"longitude": 4.8879,
"last_image_at": "2025-03-23T08:00:00Z"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/cameras/502"
},
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001235"
}
},
"_total": null,
"camera_id": 502,
"name": "Keizersgracht 44 - South",
"lcm_id": "LCM-001235",
"pole_number": "P-043",
"status": "ONLINE",
"model": "Axis P1375",
"resolution": "1920x1080",
"latitude": 52.3705,
"longitude": 4.8882,
"last_image_at": "2025-03-23T08:00:00Z"
},
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/cameras/503"
},
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001240"
}
},
"_total": null,
"camera_id": 503,
"name": "Herengracht 108 - East",
"lcm_id": "LCM-001240",
"pole_number": "P-048",
"status": "OFFLINE",
"model": "Axis M3057-PLVE",
"resolution": "2560x1920",
"latitude": 52.3720,
"longitude": 4.8900,
"last_image_at": "2025-03-22T18:30:00Z"
}
]
},
"_total": 12
}
Lists all cameras for the given application with pagination support.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
| page | integer | No | Page number (1-indexed). Default: 1. Request successive pages until _total returns 0. |
Response Fields
| Field | Type | Description |
|---|---|---|
| camera_id | integer | Unique identifier for the camera. |
| name | string | Human-readable name of the camera, typically including location and orientation. |
| lcm_id | string | The LCM that this camera is attached to. |
| pole_number | string | The pole number where the camera is mounted. |
| status | string | Online status: ONLINE or OFFLINE. |
| model | string | Camera hardware model. |
| resolution | string | Image resolution in WIDTHxHEIGHT format. |
| latitude | float | Latitude coordinate of the camera. |
| longitude | float | Longitude coordinate of the camera. |
| last_image_at | string | ISO 8601 timestamp of when the last image was captured. |
/cameras/{camera_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/cameras/501" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/cameras/501"
},
"cameras": {
"href": "https://httpapi.sustainder.com/v2/cameras"
},
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
},
"node": {
"href": "https://httpapi.sustainder.com/v2/nodes/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
},
"_embedded": {
"node": [
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/nodes/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
},
"_total": null,
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Keizersgracht 42",
"status": "ONLINE",
"longitude": 4.8879,
"latitude": 52.3702,
"gateway_id": "352890061121289"
}
]
},
"_total": null,
"camera_id": 501,
"name": "Keizersgracht 42 - North",
"lcm_id": "LCM-001234",
"pole_number": "P-042",
"status": "ONLINE",
"model": "Axis P1375",
"resolution": "1920x1080",
"firmware_version": "10.12.114",
"latitude": 52.3702,
"longitude": 4.8879,
"orientation": "north",
"tilt_angle": 15.0,
"field_of_view": 95.0,
"night_vision": true,
"last_image_at": "2025-03-23T08:00:00Z",
"storage_used_mb": 2450,
"storage_total_mb": 32000,
"uptime_hours": 4320,
"created_at": "2024-06-15T10:00:00Z"
}
Returns detailed information for a specific camera, including hardware specifications and the embedded node data.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| camera_id | integer | The camera identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| camera_id | integer | Unique identifier for the camera. |
| name | string | Human-readable name of the camera. |
| lcm_id | string | The LCM this camera is attached to. |
| pole_number | string | The pole number where the camera is mounted. |
| status | string | Online status: ONLINE or OFFLINE. |
| model | string | Camera hardware model. |
| resolution | string | Image resolution in WIDTHxHEIGHT format. |
| firmware_version | string | Firmware version running on the camera. |
| latitude | float | Latitude coordinate. |
| longitude | float | Longitude coordinate. |
| orientation | string | Camera facing direction (e.g., north, south, east, west). |
| tilt_angle | float | Vertical tilt angle in degrees from horizontal. |
| field_of_view | float | Horizontal field of view in degrees. |
| night_vision | boolean | Whether the camera has infrared/night vision capability. |
| last_image_at | string | ISO 8601 timestamp of the last captured image. |
| storage_used_mb | integer | Local storage used in megabytes. |
| storage_total_mb | integer | Total local storage capacity in megabytes. |
| uptime_hours | integer | Total uptime hours since last reboot. |
| created_at | string | ISO 8601 timestamp of when the camera was registered. |
Error Responses
| Status | Description |
|---|---|
| 404 | Camera not found. |
/lcms/{lcm_id}/camera [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234/camera" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234/camera"
},
"lcm": {
"href": "https://httpapi.sustainder.com/v2/lcms/LCM-001234"
},
"camera": {
"href": "https://httpapi.sustainder.com/v2/cameras/501"
}
},
"_total": null,
"lcm_id": "LCM-001234",
"camera_id": 501,
"camera_name": "Keizersgracht 42 - North",
"camera_status": "ONLINE",
"camera_model": "Axis P1375",
"connection_type": "ethernet",
"power_status": "ON",
"power_consumption_watts": 12.5,
"network_bandwidth_kbps": 4500,
"last_health_check": "2025-03-23T08:05:00Z",
"health_status": "HEALTHY",
"static_files": [
{
"filename": "latest.jpg",
"description": "Most recent captured image",
"size_kb": 245,
"captured_at": "2025-03-23T08:00:00Z",
"url": "https://httpapi.sustainder.com/v2/lcms/LCM-001234/camera/static/latest.jpg"
},
{
"filename": "thumbnail.jpg",
"description": "Thumbnail of the most recent image",
"size_kb": 18,
"captured_at": "2025-03-23T08:00:00Z",
"url": "https://httpapi.sustainder.com/v2/lcms/LCM-001234/camera/static/thumbnail.jpg"
}
]
}
Returns camera controller information for a specific LCM. This shows the relationship between the LCM and its attached camera, including power delivery, network status, and available static image files.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
| camera_id | integer | The attached camera identifier. |
| camera_name | string | Name of the attached camera. |
| camera_status | string | Camera online status: ONLINE or OFFLINE. |
| camera_model | string | Camera hardware model. |
| connection_type | string | Connection type between the LCM and camera: ethernet, wifi, or serial. |
| power_status | string | Power delivery status: ON or OFF. |
| power_consumption_watts | float | Current power consumption of the camera in Watts. |
| network_bandwidth_kbps | integer | Current network bandwidth usage in kilobits per second. |
| last_health_check | string | ISO 8601 timestamp of the last health check. |
| health_status | string | Health status: HEALTHY, DEGRADED, or UNHEALTHY. |
| static_files | array | List of available static image files. |
| static_files[].filename | string | Name of the static file. |
| static_files[].description | string | Description of the file contents. |
| static_files[].size_kb | integer | File size in kilobytes. |
| static_files[].captured_at | string | ISO 8601 timestamp of when the image was captured. |
| static_files[].url | string | Full URL to retrieve the static file. |
Error Responses
| Status | Description |
|---|---|
| 404 | LCM not found or has no camera attached. |
/lcms/{lcm_id}/camera/static/{staticfile} [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234/camera/static/latest.jpg" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
--output latest.jpg
Response (200 OK)
Content-Type: image/jpeg
Content-Length: 250880
Content-Disposition: inline; filename="latest.jpg"
<binary image data>
Error Response (404 Not Found)
{
"error": "Static file not found.",
"message": "The requested file 'nonexistent.jpg' does not exist for camera on LCM-001234."
}
Retrieves a static camera file (image) from the camera attached to a specific LCM. The response is the raw binary image data with appropriate content-type headers. Use the --output flag in curl to save the image to a file.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
| staticfile | string | The filename to retrieve (e.g., latest.jpg, thumbnail.jpg). Available filenames are listed in the /lcms/{lcm_id}/camera response. |
Response Headers
| Header | Description |
|---|---|
| Content-Type | MIME type of the image (e.g., image/jpeg, image/png). |
| Content-Length | Size of the image in bytes. |
| Content-Disposition | Suggested filename for saving the image. |
Error Responses
| Status | Description |
|---|---|
| 404 | LCM not found, no camera attached, or static file does not exist. |
Errors & Issues
Endpoints for retrieving errors and issues across the system. These endpoints surface active hardware and communication errors detected on LCMs and gateways, enabling monitoring dashboards and automated alerting workflows.
/issues [GET]
Request
curl "https://httpapi.sustainder.com/v2/issues" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/issues"
}
},
"_embedded": {
"lcm_errors": [
{
"lcm_id": "LCM-001234",
"error_type": "LAMP",
"detected_on": "2024-11-14T22:15:33Z"
},
{
"lcm_id": "LCM-001891",
"error_type": "DIMMING",
"detected_on": "2024-11-15T03:42:17Z"
},
{
"lcm_id": "LCM-002045",
"error_type": "BALLAST",
"detected_on": "2024-11-13T18:09:44Z"
},
{
"lcm_id": "LCM-001567",
"error_type": "TILTED_WARNING",
"detected_on": "2024-11-15T10:28:51Z"
},
{
"lcm_id": "LCM-001102",
"error_type": "LCM_NOT_RESPONDING",
"detected_on": "2024-11-15T08:00:02Z"
},
{
"lcm_id": "LCM-002201",
"error_type": "POWER_FAILURE",
"detected_on": "2024-11-15T11:55:30Z"
}
],
"gateway_errors": [
{
"gateway_id": "352890061121345",
"error_type": "GATEWAY_NOT_RESPONDING",
"detected_on": "2024-11-15T06:12:08Z"
}
]
},
"_total": 7
}
Lists all LCMs and gateways that currently have active errors. The response is formatted as HAL+JSON with _links for discoverability and _embedded containing two arrays: lcm_errors for lighting control module errors and gateway_errors for gateway communication errors.
Response Fields
| Field | Type | Description |
|---|---|---|
| lcm_errors | array | List of LCM error objects. Empty array if no LCM errors are active. |
| lcm_errors[].lcm_id | string | The unique LCM identifier experiencing the error. |
| lcm_errors[].error_type | string | The type of error detected. See Error Types below. |
| lcm_errors[].detected_on | string | ISO 8601 timestamp of when the error was first detected. |
| gateway_errors | array | List of gateway error objects. Empty array if no gateway errors are active. |
| gateway_errors[].gateway_id | string | The unique gateway identifier (typically an IMEI number). |
| gateway_errors[].error_type | string | The type of error detected. See Error Types below. |
| gateway_errors[].detected_on | string | ISO 8601 timestamp of when the error was first detected. |
Error Types
| Error Type | Applies To | Description |
|---|---|---|
DIMMING |
LCM | The LCM is unable to dim correctly. The driver or dimming circuit may be malfunctioning. |
BALLAST |
LCM | A ballast or LED driver failure has been detected. |
LAMP |
LCM | The lamp or LED module is not functioning. |
LCM_NOT_RESPONDING |
LCM | The LCM has stopped communicating with its gateway. |
GATEWAY_NOT_RESPONDING |
Gateway | The gateway has stopped communicating with the SBL. |
TILTED_WARNING |
LCM | The luminaire tilt angle exceeds the warning threshold. |
TILTED_ERROR |
LCM | The luminaire tilt angle exceeds the critical error threshold. Possible pole damage or vandalism. |
POWER_FAILURE |
LCM | A power supply failure has been detected on the LCM. |
/errors [GET]
Request
curl "https://httpapi.sustainder.com/v2/errors" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/errors"
}
},
"_embedded": {
"lcm_errors": [
{
"lcm_id": "LCM-001234",
"error_type": "LAMP",
"detected_on": "2024-11-14T22:15:33Z"
},
{
"lcm_id": "LCM-001891",
"error_type": "DIMMING",
"detected_on": "2024-11-15T03:42:17Z"
}
],
"gateway_errors": [
{
"gateway_id": "352890061121345",
"error_type": "GATEWAY_NOT_RESPONDING",
"detected_on": "2024-11-15T06:12:08Z"
}
]
},
"_total": 3
}
Alias of the /issues endpoint. Returns the same response structure and data. This endpoint exists for backward compatibility.
Response Fields
Same as /issues.
/device-errors [GET]
Request
curl "https://httpapi.sustainder.com/v2/device-errors?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/device-errors?application_artkey=35"
}
},
"_embedded": {
"device_errors": [
{
"id": 48291,
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"lcm_id": "LCM-001234",
"error_type": "LAMP",
"resolved_on": null,
"detected_on": "2024-11-14T22:15:33Z"
},
{
"id": 48187,
"node_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"lcm_id": "LCM-001891",
"error_type": "DIMMING",
"resolved_on": null,
"detected_on": "2024-11-15T03:42:17Z"
},
{
"id": 47953,
"node_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"lcm_id": "LCM-002045",
"error_type": "BALLAST",
"resolved_on": "2024-11-15T09:30:00Z",
"detected_on": "2024-11-13T18:09:44Z"
},
{
"id": 47801,
"node_id": "d4e5f6a7-b8c9-0123-defa-234567890123",
"lcm_id": "LCM-001567",
"error_type": "TILTED_ERROR",
"resolved_on": "2024-11-14T14:22:10Z",
"detected_on": "2024-11-12T07:45:19Z"
},
{
"id": 47650,
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"lcm_id": "LCM-001234",
"error_type": "POWER_FAILURE",
"resolved_on": "2024-11-13T16:05:42Z",
"detected_on": "2024-11-13T11:20:55Z"
}
]
},
"_total": 5
}
Lists all device errors for a given application, including both active and resolved errors. Each error record includes the detection timestamp and, if applicable, the resolution timestamp.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier to retrieve errors for. |
Response Fields
| Field | Type | Description |
|---|---|---|
| id | integer | Unique error record identifier (artkey). |
| node_id | string | The UUID of the node (UDID) where the error occurred. |
| lcm_id | string | The LCM identifier associated with the error. |
| error_type | string | The type of error. See Error Types. |
| resolved_on | string\ | null |
| detected_on | string | ISO 8601 timestamp of when the error was first detected. |
Error Responses
| Status | Description |
|---|---|
| 400 | Missing or invalid application_artkey parameter. |
/device-errors/{device_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/device-errors/LCM-001234" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/device-errors/LCM-001234"
}
},
"_embedded": {
"device_errors": [
{
"id": 48291,
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"lcm_id": "LCM-001234",
"error_type": "LAMP",
"resolved_on": null,
"detected_on": "2024-11-14T22:15:33Z"
},
{
"id": 47650,
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"lcm_id": "LCM-001234",
"error_type": "POWER_FAILURE",
"resolved_on": "2024-11-13T16:05:42Z",
"detected_on": "2024-11-13T11:20:55Z"
},
{
"id": 46820,
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"lcm_id": "LCM-001234",
"error_type": "LCM_NOT_RESPONDING",
"resolved_on": "2024-11-10T08:14:23Z",
"detected_on": "2024-11-09T23:58:01Z"
}
]
},
"_total": 3
}
Returns all errors (active and resolved) for a specific device, identified by its LCM ID. This is useful for viewing the complete error history of a single luminaire.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| device_id | string | The LCM identifier of the device (e.g., LCM-001234). |
Response Fields
Same fields as /device-errors. The response is filtered to only include errors matching the specified device.
Error Responses
| Status | Description |
|---|---|
| 404 | Device not found. |
Alarm Logbook
The alarm logbook provides a historical record of alarm events across the system. Each entry represents a significant event such as an error being detected, an error being resolved, a device going offline, or a device recovering. Use this endpoint to build audit trails, generate reports, or feed external monitoring systems.
/alarm-logbook [GET]
Request
curl "https://httpapi.sustainder.com/v2/alarm-logbook?application_artkey=35&page=1&pageSize=25" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/alarm-logbook?application_artkey=35&page=1&pageSize=25"
},
"next": {
"href": "https://httpapi.sustainder.com/v2/alarm-logbook?application_artkey=35&page=2&pageSize=25"
}
},
"_embedded": {
"alarm_events": [
{
"id": 109284,
"device_id": "LCM-001234",
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"node_name": "Keizersgracht 42",
"event_type": "ERROR_DETECTED",
"error_type": "LAMP",
"severity": "critical",
"message": "Lamp failure detected on LCM-001234",
"timestamp": "2024-11-14T22:15:33Z"
},
{
"id": 109271,
"device_id": "LCM-002045",
"node_id": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"node_name": "Vondelpark Pad 7",
"event_type": "ERROR_RESOLVED",
"error_type": "BALLAST",
"severity": "info",
"message": "Ballast error resolved on LCM-002045",
"timestamp": "2024-11-14T21:30:00Z"
},
{
"id": 109265,
"device_id": "352890061121345",
"node_id": "e5f6a7b8-c9d0-1234-efab-345678901234",
"node_name": "Gateway Centrum-Zuid",
"event_type": "DEVICE_OFFLINE",
"error_type": "GATEWAY_NOT_RESPONDING",
"severity": "warning",
"message": "Gateway 352890061121345 is not responding",
"timestamp": "2024-11-14T20:12:08Z"
},
{
"id": 109250,
"device_id": "LCM-001567",
"node_id": "d4e5f6a7-b8c9-0123-defa-234567890123",
"node_name": "Herengracht 108",
"event_type": "ERROR_DETECTED",
"error_type": "TILTED_WARNING",
"severity": "warning",
"message": "Tilt warning threshold exceeded on LCM-001567",
"timestamp": "2024-11-14T18:28:51Z"
},
{
"id": 109231,
"device_id": "LCM-002201",
"node_id": "f6a7b8c9-d0e1-2345-fabc-456789012345",
"node_name": "Prinsengracht 215",
"event_type": "ERROR_DETECTED",
"error_type": "POWER_FAILURE",
"severity": "critical",
"message": "Power failure detected on LCM-002201",
"timestamp": "2024-11-14T15:55:30Z"
}
]
},
"_total": 1342,
"_page": 1,
"_pageSize": 25
}
Returns a paginated list of historical alarm events for the specified application. Events are returned in reverse chronological order (newest first).
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| application_artkey | integer | Yes | - | The application identifier to retrieve alarm events for. |
| page | integer | No | 1 | Page number (1-indexed). |
| pageSize | integer | No | 25 | Number of results per page. Maximum value is 100. |
| from | string | No | - | Filter events from this ISO 8601 datetime onward (e.g., 2024-11-01T00:00:00Z). |
| to | string | No | - | Filter events up to this ISO 8601 datetime (e.g., 2024-11-15T23:59:59Z). |
Request with date filters
curl "https://httpapi.sustainder.com/v2/alarm-logbook?application_artkey=35&from=2024-11-01T00:00:00Z&to=2024-11-15T23:59:59Z&page=1&pageSize=50" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/alarm-logbook?application_artkey=35&from=2024-11-01T00:00:00Z&to=2024-11-15T23:59:59Z&page=1&pageSize=50"
}
},
"_embedded": {
"alarm_events": [
{
"id": 109284,
"device_id": "LCM-001234",
"node_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"node_name": "Keizersgracht 42",
"event_type": "ERROR_DETECTED",
"error_type": "LAMP",
"severity": "critical",
"message": "Lamp failure detected on LCM-001234",
"timestamp": "2024-11-14T22:15:33Z"
},
{
"id": 108910,
"device_id": "LCM-001891",
"node_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"node_name": "Herengracht 108",
"event_type": "DEVICE_ONLINE",
"error_type": null,
"severity": "info",
"message": "LCM-001891 is back online",
"timestamp": "2024-11-10T14:05:22Z"
}
]
},
"_total": 87,
"_page": 1,
"_pageSize": 50
}
Response Fields
| Field | Type | Description |
|---|---|---|
| id | integer | Unique alarm event identifier. |
| device_id | string | The device identifier (LCM ID or gateway ID) that triggered the event. |
| node_id | string | The UUID of the node associated with the event. |
| node_name | string | Display name of the node for convenience. |
| event_type | string | The category of alarm event. See Event Types below. |
| error_type | string\ | null |
| severity | string | Severity level: critical, warning, or info. |
| message | string | Human-readable description of the event. |
| timestamp | string | ISO 8601 timestamp of when the event occurred. |
Pagination Fields
| Field | Type | Description |
|---|---|---|
| _total | integer | Total number of alarm events matching the query. |
| _page | integer | Current page number. |
| _pageSize | integer | Number of results per page. |
Event Types
| Event Type | Description |
|---|---|
ERROR_DETECTED |
A new error has been detected on a device. |
ERROR_RESOLVED |
A previously active error has been resolved. |
DEVICE_OFFLINE |
A device has gone offline and is no longer communicating. |
DEVICE_ONLINE |
A previously offline device has come back online. |
Error Responses
| Status | Description |
|---|---|
| 400 | Missing or invalid application_artkey, or invalid date filter format. |
Types
/types/error [GET]
Request
curl "https://httpapi.sustainder.com/v2/types/error" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/types/error"
}
},
"_total": null,
"lcm_error_types": [
"DIMMING",
"BALLAST",
"LAMP",
"LCM_NOT_RESPONDING",
"GATEWAY_NOT_RESPONDING",
"TILTED_WARNING",
"TILTED_ERROR",
"POWER_FAILURE"
],
"gateway_error_types": [
"GATEWAY_NOT_RESPONDING"
]
}
Lists all possible error types that can be reported by devices in the SBL.
Response Fields
| Field | Type | Description |
|---|---|---|
| lcm_error_types | array | Error types that can occur on LCMs. |
| gateway_error_types | array | Error types that can occur on gateways. |
LCM Error Types
| Error Type | Description |
|---|---|
DIMMING |
The LCM is not dimming correctly. |
BALLAST |
Ballast/driver failure detected. |
LAMP |
Lamp failure — the light source is not functioning. |
LCM_NOT_RESPONDING |
The LCM has stopped communicating with the gateway. |
GATEWAY_NOT_RESPONDING |
The gateway this LCM connects through is not responding. |
TILTED_WARNING |
Tilt angle exceeds the warning threshold. |
TILTED_ERROR |
Tilt angle exceeds the error threshold (potential vandalism or damage). |
POWER_FAILURE |
Power supply failure detected. |
Gateway Error Types
| Error Type | Description |
|---|---|
GATEWAY_NOT_RESPONDING |
The gateway has stopped communicating with the SBL. |
Device Notes & Comments
Notes and comments can be attached to LCMs to track maintenance history, installation observations, and operational remarks. Notes are intended for internal technical records, while comments are designed for general-purpose annotations visible to all users with access to the device.
/lcms/{lcm_id}/notes [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234/notes" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
[
{
"id": 142,
"note": "Replaced LED driver board due to flickering at low dimming levels. Old driver model: DALI-2 v1.3, new driver: DALI-2 v2.0.",
"user": "j.devries@gemeente-amsterdam.nl",
"created_at": "2025-09-12T08:45:22Z",
"updated_at": "2025-09-12T08:45:22Z"
},
{
"id": 138,
"note": "Pole tilt angle corrected after storm damage inspection. Recalibrated accelerometer.",
"user": "m.bakker@sustainder.com",
"created_at": "2025-08-30T14:12:05Z",
"updated_at": "2025-09-01T09:30:00Z"
},
{
"id": 97,
"note": "Initial installation completed. Firmware version 2.4.1 confirmed.",
"user": "t.jansen@sustainder.com",
"created_at": "2025-06-15T10:20:33Z",
"updated_at": "2025-06-15T10:20:33Z"
}
]
Returns all notes attached to a specific LCM, ordered by creation date (most recent first).
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| id | integer | Unique identifier for the note. |
| note | string | The note text content. |
| user | string | Email address of the user who created the note. |
| created_at | string | ISO 8601 timestamp when the note was created. |
| updated_at | string | ISO 8601 timestamp when the note was last updated. |
Error Responses
| Status | Description |
|---|---|
| 404 | LCM not found. |
/lcms/{lcm_id}/notes [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/lcms/LCM-001234/notes" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"note": "Scheduled maintenance completed. Cleaned optical lens and verified all cable connections."
}'
Request body
{
"note": "Scheduled maintenance completed. Cleaned optical lens and verified all cable connections."
}
Response (201 Created)
{
"message": "Note created successfully.",
"note_id": 156
}
Creates a new note on a specific LCM. The note is automatically associated with the authenticated user.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| note | string | Yes | The note text content. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., empty note text). |
| 404 | LCM not found. |
/lcms/{lcm_id}/notes/{note_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234/notes/142" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"id": 142,
"note": "Replaced LED driver board due to flickering at low dimming levels. Old driver model: DALI-2 v1.3, new driver: DALI-2 v2.0.",
"user": "j.devries@gemeente-amsterdam.nl",
"created_at": "2025-09-12T08:45:22Z",
"updated_at": "2025-09-12T08:45:22Z"
}
Returns a single note by its ID.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
| note_id | integer | The note identifier. |
Error Responses
| Status | Description |
|---|---|
| 404 | LCM or note not found. |
/lcms/{lcm_id}/notes/{note_id} [PUT]
Request
curl -X PUT "https://httpapi.sustainder.com/v2/lcms/LCM-001234/notes/142" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"note": "Replaced LED driver board due to flickering at low dimming levels. Old driver model: DALI-2 v1.3, new driver: DALI-2 v2.0. Follow-up inspection scheduled for 2025-12-01."
}'
Request body
{
"note": "Replaced LED driver board due to flickering at low dimming levels. Old driver model: DALI-2 v1.3, new driver: DALI-2 v2.0. Follow-up inspection scheduled for 2025-12-01."
}
Response (200 OK)
{
"id": 142,
"note": "Replaced LED driver board due to flickering at low dimming levels. Old driver model: DALI-2 v1.3, new driver: DALI-2 v2.0. Follow-up inspection scheduled for 2025-12-01.",
"user": "j.devries@gemeente-amsterdam.nl",
"created_at": "2025-09-12T08:45:22Z",
"updated_at": "2025-11-20T16:05:47Z"
}
Updates the text content of an existing note. Only the user who created the note or an admin can update it. The updated_at timestamp is automatically refreshed.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
| note_id | integer | The note identifier. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| note | string | Yes | The updated note text content. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., empty note text). |
| 403 | Not authorized to update this note. |
| 404 | LCM or note not found. |
/lcms/{lcm_id}/notes/{note_id} [DELETE]
Request
curl -X DELETE "https://httpapi.sustainder.com/v2/lcms/LCM-001234/notes/142" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"message": "Note deleted successfully."
}
Deletes a note from an LCM. This action is restricted to admin users only.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
| note_id | integer | The note identifier. |
Error Responses
| Status | Description |
|---|---|
| 403 | Forbidden. Only admin users can delete notes. |
| 404 | LCM or note not found. |
/lcms/{lcm_id}/comments [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234/comments" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
[
{
"id": 87,
"comment": "This luminaire is located near a school crossing. Consider increasing light levels during early morning hours.",
"user": "k.vanderberg@gemeente-amsterdam.nl",
"created_at": "2025-10-05T11:30:18Z",
"updated_at": "2025-10-05T11:30:18Z"
},
{
"id": 64,
"comment": "Residents on this street have requested warmer light color. Forwarded to planning department.",
"user": "s.mulder@gemeente-amsterdam.nl",
"created_at": "2025-08-22T09:15:42Z",
"updated_at": "2025-08-25T13:20:00Z"
}
]
Returns all comments attached to a specific LCM, ordered by creation date (most recent first). Comments follow the same structure as notes but use the comment field instead of note.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| id | integer | Unique identifier for the comment. |
| comment | string | The comment text content. |
| user | string | Email address of the user who created the comment. |
| created_at | string | ISO 8601 timestamp when the comment was created. |
| updated_at | string | ISO 8601 timestamp when the comment was last updated. |
Error Responses
| Status | Description |
|---|---|
| 404 | LCM not found. |
/lcms/{lcm_id}/comments [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/lcms/LCM-001234/comments" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"comment": "Light output appears dimmer than neighboring luminaires. Possible optical degradation — recommend inspection."
}'
Request body
{
"comment": "Light output appears dimmer than neighboring luminaires. Possible optical degradation — recommend inspection."
}
Response (201 Created)
{
"message": "Comment created successfully.",
"comment_id": 93
}
Creates a new comment on a specific LCM. The comment is automatically associated with the authenticated user.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| comment | string | Yes | The comment text content. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., empty comment text). |
| 404 | LCM not found. |
/lcms/{lcm_id}/comments/{comment_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234/comments/87" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"id": 87,
"comment": "This luminaire is located near a school crossing. Consider increasing light levels during early morning hours.",
"user": "k.vanderberg@gemeente-amsterdam.nl",
"created_at": "2025-10-05T11:30:18Z",
"updated_at": "2025-10-05T11:30:18Z"
}
Returns a single comment by its ID.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
| comment_id | integer | The comment identifier. |
Error Responses
| Status | Description |
|---|---|
| 404 | LCM or comment not found. |
/lcms/{lcm_id}/comments/{comment_id} [PUT]
Request
curl -X PUT "https://httpapi.sustainder.com/v2/lcms/LCM-001234/comments/87" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"comment": "This luminaire is located near a school crossing. Light levels increased to 100% between 07:00-08:30 per municipal request REF-2025-4412."
}'
Request body
{
"comment": "This luminaire is located near a school crossing. Light levels increased to 100% between 07:00-08:30 per municipal request REF-2025-4412."
}
Response (200 OK)
{
"id": 87,
"comment": "This luminaire is located near a school crossing. Light levels increased to 100% between 07:00-08:30 per municipal request REF-2025-4412.",
"user": "k.vanderberg@gemeente-amsterdam.nl",
"created_at": "2025-10-05T11:30:18Z",
"updated_at": "2025-11-18T10:42:33Z"
}
Updates the text content of an existing comment. Only the user who created the comment or an admin can update it. The updated_at timestamp is automatically refreshed.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
| comment_id | integer | The comment identifier. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| comment | string | Yes | The updated comment text content. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., empty comment text). |
| 403 | Not authorized to update this comment. |
| 404 | LCM or comment not found. |
/lcms/{lcm_id}/comments/{comment_id} [DELETE]
Request
curl -X DELETE "https://httpapi.sustainder.com/v2/lcms/LCM-001234/comments/87" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"message": "Comment deleted successfully."
}
Deletes a comment from an LCM. This action is restricted to admin users only.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
| comment_id | integer | The comment identifier. |
Error Responses
| Status | Description |
|---|---|
| 403 | Forbidden. Only admin users can delete comments. |
| 404 | LCM or comment not found. |
Device Logbook
The device logbook records the settings history for LCMs, providing an audit trail of configuration changes over time. Each logbook entry captures what setting was changed, who changed it, when the change expired, and whether it was successfully synchronized to the device. Entries for LIGHTING_MODE and DIRECT_CONTROL_LIGHT_LEVEL types are excluded from the logbook.
/lcms/{lcm_id}/logbook [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234/logbook" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
[
{
"artkey": 4821,
"type": "MAX_LIGHT_LEVEL",
"expired_at": "2025-11-10T22:00:00Z",
"current_value": "[85, 85, 85, 85]",
"desired_value": "[100, 100, 100, 100]",
"sync_attempts": 1,
"number_of_requests": 1,
"additional_data": null,
"force_sync": false,
"user": "j.devries@gemeente-amsterdam.nl",
"comments": [
{
"id": 312,
"comment": "Increased light levels back to 100% for winter season.",
"user": "j.devries@gemeente-amsterdam.nl",
"created_at": "2025-11-10T14:30:00Z"
}
]
},
{
"artkey": 4790,
"type": "DIMMING_SCHEME",
"expired_at": "2025-10-25T18:15:00Z",
"current_value": "Summer Schedule",
"desired_value": "Winter Schedule",
"sync_attempts": 2,
"number_of_requests": 1,
"additional_data": "{\"calendar_name\": \"Winter Schedule\", \"queue_when_offline\": true}",
"force_sync": false,
"user": "m.bakker@sustainder.com",
"comments": []
},
{
"artkey": 4685,
"type": "MAX_LIGHT_LEVEL",
"expired_at": "2025-09-15T12:00:00Z",
"current_value": "[100, 100, 100, 100]",
"desired_value": "[85, 85, 85, 85]",
"sync_attempts": 1,
"number_of_requests": 1,
"additional_data": null,
"force_sync": true,
"user": "t.jansen@sustainder.com",
"comments": [
{
"id": 298,
"comment": "Reduced light levels for summer energy savings program.",
"user": "t.jansen@sustainder.com",
"created_at": "2025-09-15T10:05:22Z"
},
{
"id": 301,
"comment": "Confirmed change synced successfully to device.",
"user": "m.bakker@sustainder.com",
"created_at": "2025-09-15T10:12:45Z"
}
]
}
]
Returns the last 10 settings history entries for a specific LCM, ordered by most recent first. Entries with type LIGHTING_MODE and DIRECT_CONTROL_LIGHT_LEVEL are excluded from the response.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| artkey | integer | Unique identifier for the logbook entry. |
| type | string | The type of setting that was changed (e.g., MAX_LIGHT_LEVEL, DIMMING_SCHEME, CLO_SETTING, MOTION_PROFILE). |
| expired_at | string\ | null |
| current_value | string | The value of the setting before the change was applied. |
| desired_value | string | The target value that was requested. |
| sync_attempts | integer | Number of attempts made to synchronize the setting to the device. |
| number_of_requests | integer | Number of times this setting change was requested. |
| additional_data | string\ | null |
| force_sync | boolean | Whether the setting change was forced, bypassing normal queue behavior. |
| user | string | Email address of the user who initiated the setting change. |
| comments | array | List of comments attached to this logbook entry. |
Comment Object
| Field | Type | Description |
|---|---|---|
| id | integer | Unique identifier for the comment. |
| comment | string | The comment text content. |
| user | string | Email address of the user who wrote the comment. |
| created_at | string | ISO 8601 timestamp when the comment was created. |
Error Responses
| Status | Description |
|---|---|
| 404 | LCM not found. |
/lcms/{lcm_id}/logbook/comments [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234/logbook/comments" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
[
{
"id": 312,
"setting_artkey": 4821,
"comment": "Increased light levels back to 100% for winter season.",
"user": "j.devries@gemeente-amsterdam.nl",
"created_at": "2025-11-10T14:30:00Z"
},
{
"id": 301,
"setting_artkey": 4685,
"comment": "Confirmed change synced successfully to device.",
"user": "m.bakker@sustainder.com",
"created_at": "2025-09-15T10:12:45Z"
},
{
"id": 298,
"setting_artkey": 4685,
"comment": "Reduced light levels for summer energy savings program.",
"user": "t.jansen@sustainder.com",
"created_at": "2025-09-15T10:05:22Z"
}
]
Returns all logbook comments for a specific LCM across all logbook entries, ordered by most recent first.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| id | integer | Unique identifier for the comment. |
| setting_artkey | integer | The artkey of the logbook entry this comment belongs to. |
| comment | string | The comment text content. |
| user | string | Email address of the user who wrote the comment. |
| created_at | string | ISO 8601 timestamp when the comment was created. |
Error Responses
| Status | Description |
|---|---|
| 404 | LCM not found. |
/lcms/{lcm_id}/logbook/comments [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/lcms/LCM-001234/logbook/comments" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"setting_artkey": 4821,
"comment": "Verified new light levels during evening inspection. Output is consistent across all four channels."
}'
Request body
{
"setting_artkey": 4821,
"comment": "Verified new light levels during evening inspection. Output is consistent across all four channels."
}
Response (201 Created)
{
"message": "Comment created successfully.",
"comment_id": 325
}
Adds a comment to a specific logbook entry. The comment is automatically associated with the authenticated user. Use the setting_artkey to reference the logbook entry the comment pertains to.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| setting_artkey | integer | Yes | The artkey of the logbook entry to attach the comment to. |
| comment | string | Yes | The comment text content. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., empty comment text or invalid setting_artkey). |
| 404 | LCM or logbook entry not found. |
Dummy Devices
Dummy devices are placeholder entries used for planning and preparing installations before physical hardware is deployed. They allow project managers to define device locations, assign metadata, and organize infrastructure layouts in advance. Once real devices are installed at the planned locations, the dummy devices can be replaced with actual device records.
/dummy-devices/import [POST]
Request (JSON with Base64)
curl -X POST "https://httpapi.sustainder.com/v2/dummy-devices/import" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"file_base64": "UEsDBBQAAAAIAGFiV1kAAA..."
}'
Request (Direct file upload)
curl -X POST "https://httpapi.sustainder.com/v2/dummy-devices/import" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" \
--data-binary @dummy-devices-plan.xlsx
Response (200 OK)
{
"message": "Import completed. 12 created, 3 updated, 1 error.",
"created": [
{
"id": 501,
"name": "Prinsengracht 88",
"latitude": 52.3681,
"longitude": 4.8832,
"model": "alexia"
},
{
"id": 502,
"name": "Prinsengracht 90",
"latitude": 52.3683,
"longitude": 4.8835,
"model": "alexia"
},
{
"id": 503,
"name": "Prinsengracht 92",
"latitude": 52.3685,
"longitude": 4.8837,
"model": "bianca"
}
],
"updated": [
{
"id": 487,
"name": "Herengracht 55",
"latitude": 52.3715,
"longitude": 4.8912,
"model": "alexia"
}
],
"errors": [
{
"row": 14,
"message": "Invalid coordinate system 'WGS85'. Supported values: WGS84, RD."
}
]
}
Imports dummy devices from an Excel spreadsheet. The file can be uploaded either as a direct binary upload with the appropriate content type or as a Base64-encoded string in a JSON body. If a device with matching coordinates already exists, it will be updated rather than duplicated.
Content Types
| Content-Type | Description |
|---|---|
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
Direct Excel file upload (binary). |
application/json |
JSON body with file_base64 field containing the Base64-encoded Excel file. |
Request Parameters (JSON)
| Parameter | Type | Required | Description |
|---|---|---|---|
| file_base64 | string | Yes | Base64-encoded Excel file content. |
Required Excel Columns
| Column | Description |
|---|---|
| Latitude | Latitude coordinate of the device location. |
| Longitude | Longitude coordinate of the device location. |
| Model | The LCM model type (e.g., alexia, anne, bianca). |
| Coordinate system | The coordinate reference system used: WGS84 or RD (Rijksdriehoekscoordinaten). |
Optional Excel Columns
| Column | Description |
|---|---|
| Name | Display name for the dummy device. |
| Area | The area or district name. |
| Street | The street name where the device will be installed. |
| Pole number | The pole or mounting point identifier. |
| Pole type | The type of pole (e.g., steel, aluminum, concrete). |
| Mounting height | Height of the luminaire mounting point in meters. |
| Armature type | The luminaire armature/housing type. |
| Light color | Color temperature (e.g., 3000K, 4000K). |
| Wattage | Rated wattage of the luminaire. |
Response Fields
| Field | Type | Description |
|---|---|---|
| message | string | Summary of the import operation. |
| created | array | List of newly created dummy device records. |
| updated | array | List of existing dummy device records that were updated. |
| errors | array | List of row-level errors encountered during import. |
Error Object
| Field | Type | Description |
|---|---|---|
| row | integer | The row number in the Excel file where the error occurred. |
| message | string | Description of the validation error. |
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid file format or no data found in the spreadsheet. |
| 422 | All rows failed validation. |
/dummy-devices/locations [GET]
Request
curl "https://httpapi.sustainder.com/v2/dummy-devices/locations?application_artkey=35&in_production=false" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
[
{
"id": 501,
"name": "Prinsengracht 88",
"latitude": 52.3681,
"longitude": 4.8832,
"model": "alexia",
"area": "Centrum",
"street": "Prinsengracht",
"pole_number": "P-201",
"in_production": false
},
{
"id": 502,
"name": "Prinsengracht 90",
"latitude": 52.3683,
"longitude": 4.8835,
"model": "alexia",
"area": "Centrum",
"street": "Prinsengracht",
"pole_number": "P-202",
"in_production": false
},
{
"id": 503,
"name": "Prinsengracht 92",
"latitude": 52.3685,
"longitude": 4.8837,
"model": "bianca",
"area": "Centrum",
"street": "Prinsengracht",
"pole_number": "P-203",
"in_production": false
},
{
"id": 487,
"name": "Herengracht 55",
"latitude": 52.3715,
"longitude": 4.8912,
"model": "alexia",
"area": "Centrum",
"street": "Herengracht",
"pole_number": "P-120",
"in_production": true
}
]
Returns a list of all dummy device locations. Use query parameters to filter by application and production status.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| application_artkey | integer | - | Filter dummy devices by application identifier. |
| in_production | boolean | - | Filter by production status. false returns devices not yet in production; true returns devices that have been replaced by real hardware. |
Response Fields
| Field | Type | Description |
|---|---|---|
| id | integer | Unique identifier for the dummy device. |
| name | string | Display name of the dummy device. |
| latitude | float | Latitude coordinate (WGS84). |
| longitude | float | Longitude coordinate (WGS84). |
| model | string | The planned LCM model type. |
| area | string | The area or district name. |
| street | string | The street name. |
| pole_number | string | The pole or mounting point identifier. |
| in_production | boolean | Whether a real device has been installed at this location. |
/dummy-devices/template [GET]
Request
curl "https://httpapi.sustainder.com/v2/dummy-devices/template" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
--output dummy-devices-template.xlsx
Response (200 OK)
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Disposition: attachment; filename="dummy-devices-template.xlsx"
Downloads an Excel template file pre-formatted with all supported columns and example data. Use this template to prepare dummy device import files.
The template includes the following columns: Latitude, Longitude, Model, Coordinate system, Name, Area, Street, Pole number, Pole type, Mounting height, Armature type, Light color, and Wattage.
/dummy-devices/{device_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/dummy-devices/501" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"id": 501,
"name": "Prinsengracht 88",
"latitude": 52.3681,
"longitude": 4.8832,
"model": "alexia",
"coordinate_system": "WGS84",
"area": "Centrum",
"street": "Prinsengracht",
"pole_number": "P-201",
"pole_type": "steel",
"mounting_height": "8.0m",
"armature_type": "Philips Luma",
"light_color": "3000K",
"wattage": 45,
"in_production": false,
"application_artkey": 35,
"created_at": "2025-10-01T09:15:00Z",
"updated_at": "2025-10-01T09:15:00Z"
}
Returns detailed information for a specific dummy device.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| device_id | integer | The dummy device identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| id | integer | Unique identifier for the dummy device. |
| name | string | Display name of the dummy device. |
| latitude | float | Latitude coordinate. |
| longitude | float | Longitude coordinate. |
| model | string | The planned LCM model type. |
| coordinate_system | string | The coordinate reference system: WGS84 or RD. |
| area | string | The area or district name. |
| street | string | The street name. |
| pole_number | string | The pole or mounting point identifier. |
| pole_type | string | The type of pole. |
| mounting_height | string | Height of the luminaire mounting point. |
| armature_type | string | The luminaire armature/housing type. |
| light_color | string | Color temperature. |
| wattage | integer | Rated wattage of the luminaire. |
| in_production | boolean | Whether a real device has been installed at this location. |
| application_artkey | integer | The application this dummy device belongs to. |
| created_at | string | ISO 8601 timestamp when the record was created. |
| updated_at | string | ISO 8601 timestamp when the record was last updated. |
Error Responses
| Status | Description |
|---|---|
| 404 | Dummy device not found. |
/dummy-devices/{device_id} [PUT]
Request
curl -X PUT "https://httpapi.sustainder.com/v2/dummy-devices/501" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"name": "Prinsengracht 88 - Updated",
"latitude": 52.3682,
"longitude": 4.8833,
"model": "bianca",
"pole_number": "P-201A",
"mounting_height": "10.0m"
}'
Request body
{
"name": "Prinsengracht 88 - Updated",
"latitude": 52.3682,
"longitude": 4.8833,
"model": "bianca",
"pole_number": "P-201A",
"mounting_height": "10.0m"
}
Response (200 OK)
{
"id": 501,
"name": "Prinsengracht 88 - Updated",
"latitude": 52.3682,
"longitude": 4.8833,
"model": "bianca",
"coordinate_system": "WGS84",
"area": "Centrum",
"street": "Prinsengracht",
"pole_number": "P-201A",
"pole_type": "steel",
"mounting_height": "10.0m",
"armature_type": "Philips Luma",
"light_color": "3000K",
"wattage": 45,
"in_production": false,
"application_artkey": 35,
"created_at": "2025-10-01T09:15:00Z",
"updated_at": "2025-11-20T14:22:38Z"
}
Updates the properties of an existing dummy device. Only the fields included in the request body are updated; omitted fields retain their current values.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| device_id | integer | The dummy device identifier. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| name | string | No | Updated display name. |
| latitude | float | No | Updated latitude coordinate. |
| longitude | float | No | Updated longitude coordinate. |
| model | string | No | Updated LCM model type. |
| coordinate_system | string | No | Updated coordinate system: WGS84 or RD. |
| area | string | No | Updated area or district name. |
| street | string | No | Updated street name. |
| pole_number | string | No | Updated pole identifier. |
| pole_type | string | No | Updated pole type. |
| mounting_height | string | No | Updated mounting height. |
| armature_type | string | No | Updated armature type. |
| light_color | string | No | Updated color temperature. |
| wattage | integer | No | Updated wattage. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., invalid coordinate system). |
| 404 | Dummy device not found. |
/dummy-devices/{device_id} [DELETE]
Request
curl -X DELETE "https://httpapi.sustainder.com/v2/dummy-devices/501" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"message": "Dummy device deleted successfully."
}
Deletes a dummy device record. This is typically done after a real device has been installed at the planned location, or when the installation plan has changed.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| device_id | integer | The dummy device identifier. |
Error Responses
| Status | Description |
|---|---|
| 404 | Dummy device not found. |
Node Replacements
Node replacement jobs track the process of swapping one physical device for another at the same location. When the system detects that a new device has been installed where an existing device was previously operating, it creates a replacement job. These jobs must be reviewed and confirmed by an admin to finalize the transition, ensuring continuity of settings, history, and group memberships.
/node-replacement-jobs [GET]
Request
curl "https://httpapi.sustainder.com/v2/node-replacement-jobs?status=pending&start_date=2025-10-01&end_date=2025-11-30" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"status": "success",
"message": "3 replacement jobs found.",
"data": [
{
"artkey": 1042,
"device_artkey": 8734,
"device_lcm_id": "LCM-001234",
"device_name": "Keizersgracht 42",
"device_pole_number": "P-042",
"device_street": "Keizersgracht",
"replace_device_artkey": 9102,
"replace_device_lcm_id": "LCM-005678",
"replace_device_name": "Keizersgracht 42 - New",
"replace_device_pole_number": "P-042",
"replace_device_street": "Keizersgracht",
"detected_on": "2025-11-15T08:22:14Z"
},
{
"artkey": 1038,
"device_artkey": 8510,
"device_lcm_id": "LCM-002345",
"device_name": "Herengracht 108",
"device_pole_number": "P-108",
"device_street": "Herengracht",
"replace_device_artkey": 9088,
"replace_device_lcm_id": "LCM-006789",
"replace_device_name": "Herengracht 108 - New",
"replace_device_pole_number": "P-108",
"replace_device_street": "Herengracht",
"detected_on": "2025-11-12T14:05:33Z"
},
{
"artkey": 1035,
"device_artkey": 8291,
"device_lcm_id": "LCM-003456",
"device_name": "Vondelpark Pad 7",
"device_pole_number": "VP-007",
"device_street": "Vondelpark",
"replace_device_artkey": 9076,
"replace_device_lcm_id": "LCM-007890",
"replace_device_name": "Vondelpark Pad 7 - New",
"replace_device_pole_number": "VP-007",
"replace_device_street": "Vondelpark",
"detected_on": "2025-10-28T11:30:47Z"
}
]
}
Returns a list of node replacement jobs, filtered by status and/or date range. Each job contains the details of both the original device being replaced and the new replacement device.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| status | string | - | Filter by job status: pending, in_progress, completed, or failed. |
| start_date | string | - | Filter jobs detected on or after this date (format: YYYY-MM-DD). |
| end_date | string | - | Filter jobs detected on or before this date (format: YYYY-MM-DD). |
Response Fields
| Field | Type | Description |
|---|---|---|
| status | string | Response status: success or error. |
| message | string | Human-readable summary of the result. |
| data | array | List of replacement job objects. |
Replacement Job Object
| Field | Type | Description |
|---|---|---|
| artkey | integer | Unique identifier for the replacement job. |
| device_artkey | integer | Internal identifier of the original device being replaced. |
| device_lcm_id | string | LCM ID of the original device. |
| device_name | string | Display name of the original device. |
| device_pole_number | string | Pole number of the original device. |
| device_street | string | Street location of the original device. |
| replace_device_artkey | integer | Internal identifier of the new replacement device. |
| replace_device_lcm_id | string | LCM ID of the replacement device. |
| replace_device_name | string | Display name of the replacement device. |
| replace_device_pole_number | string | Pole number of the replacement device. |
| replace_device_street | string | Street location of the replacement device. |
| detected_on | string | ISO 8601 timestamp when the replacement was detected by the system. |
Job Status Values
| Status | Description |
|---|---|
pending |
Replacement detected but not yet reviewed. |
in_progress |
Replacement is being processed (settings transfer in progress). |
completed |
Replacement confirmed and all settings transferred successfully. |
failed |
Replacement process encountered an error. |
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid query parameters (e.g., malformed date format). |
/node-replacement-jobs/{job_id}/confirm [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/node-replacement-jobs/1042/confirm" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json"
Response (200 OK)
{
"status": "success",
"message": "Replacement job confirmed. Settings transfer initiated.",
"data": {
"artkey": 1042,
"device_artkey": 8734,
"replace_device_artkey": 9102,
"detected_on": "2025-11-15T08:22:14Z"
}
}
Confirms a pending node replacement job, initiating the transfer of settings, group memberships, and historical data from the original device to the replacement device. This action is restricted to admin users only.
When a replacement is confirmed:
- The original device's dimming scheme, light levels, and other settings are transferred to the replacement device.
- Group memberships are migrated to the replacement device.
- The original device is marked as decommissioned.
- The replacement device inherits the location metadata (name, pole number, street) from the original device.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| job_id | integer | The replacement job identifier (artkey). |
Response Fields
| Field | Type | Description |
|---|---|---|
| status | string | Response status: success or error. |
| message | string | Human-readable confirmation message. |
| data | object | Summary of the confirmed replacement job. |
| data.artkey | integer | The replacement job identifier. |
| data.device_artkey | integer | Internal identifier of the original device. |
| data.replace_device_artkey | integer | Internal identifier of the replacement device. |
| data.detected_on | string | ISO 8601 timestamp when the replacement was originally detected. |
Error Responses
| Status | Description |
|---|---|
| 403 | Forbidden. Only admin users can confirm replacement jobs. |
| 404 | Replacement job not found. |
| 409 | Job has already been confirmed or is not in a confirmable state. |
Import & Export
The import and export endpoints allow bulk transfer of system data. Exports produce downloadable files containing the full device inventory and configuration for a system. Imports allow devices to be added to a system from structured data files, with a validation step available to check data integrity before committing changes.
/system-export [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/system-export" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json"
Response (202 Accepted)
{
"message": "Export initiated. The file will be available for download shortly.",
"export_id": "exp-20251120-a3f8b2",
"status": "processing",
"created_at": "2025-11-20T14:30:00Z"
}
Initiates an asynchronous export of the system data. The export process runs in the background and produces a downloadable file containing devices, settings, group configurations, and metadata. Use the list export files endpoint to check when the export is ready for download.
Response Fields
| Field | Type | Description |
|---|---|---|
| message | string | Confirmation that the export process has started. |
| export_id | string | Unique identifier for this export job. |
| status | string | Current status of the export: processing, completed, or failed. |
| created_at | string | ISO 8601 timestamp when the export was initiated. |
Error Responses
| Status | Description |
|---|---|
| 429 | An export is already in progress. Wait for it to complete before starting another. |
/systems-export/files [GET]
Request
curl "https://httpapi.sustainder.com/v2/systems-export/files" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
[
{
"file_id": "exp-20251120-a3f8b2",
"filename": "system-export-2025-11-20.xlsx",
"status": "completed",
"file_size_bytes": 245760,
"created_at": "2025-11-20T14:30:00Z",
"completed_at": "2025-11-20T14:32:15Z",
"device_count": 342,
"expires_at": "2025-12-20T14:32:15Z"
},
{
"file_id": "exp-20251015-b7c4d1",
"filename": "system-export-2025-10-15.xlsx",
"status": "completed",
"file_size_bytes": 238592,
"created_at": "2025-10-15T10:00:00Z",
"completed_at": "2025-10-15T10:01:48Z",
"device_count": 338,
"expires_at": "2025-11-14T10:01:48Z"
},
{
"file_id": "exp-20250901-c1e5f3",
"filename": "system-export-2025-09-01.xlsx",
"status": "expired",
"file_size_bytes": 0,
"created_at": "2025-09-01T08:15:00Z",
"completed_at": "2025-09-01T08:16:33Z",
"device_count": 330,
"expires_at": "2025-10-01T08:16:33Z"
}
]
Returns a list of all available export files for the system, including their status and metadata.
Response Fields
| Field | Type | Description |
|---|---|---|
| file_id | string | Unique identifier for the export file. |
| filename | string | The generated filename of the export. |
| status | string | File status: processing, completed, failed, or expired. |
| file_size_bytes | integer | Size of the export file in bytes. 0 if expired or still processing. |
| created_at | string | ISO 8601 timestamp when the export was initiated. |
| completed_at | string\ | null |
| device_count | integer | Number of devices included in the export. |
| expires_at | string | ISO 8601 timestamp when the export file will be automatically deleted. |
File Status Values
| Status | Description |
|---|---|
processing |
Export is still being generated. |
completed |
Export is ready for download. |
failed |
Export generation failed. |
expired |
Export file has been automatically deleted after its retention period. |
/systems-export/files/{file_id} [GET]
Request
curl "https://httpapi.sustainder.com/v2/systems-export/files/exp-20251120-a3f8b2" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
--output system-export-2025-11-20.xlsx
Response (200 OK)
Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
Content-Disposition: attachment; filename="system-export-2025-11-20.xlsx"
Downloads a specific export file. The response is a binary file download (Excel format).
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| file_id | string | The export file identifier. |
Error Responses
| Status | Description |
|---|---|
| 404 | Export file not found or has expired. |
| 409 | Export is still processing and not yet available for download. |
/system-import [POST]
Request (JSON with Base64)
curl -X POST "https://httpapi.sustainder.com/v2/system-import" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"file_base64": "UEsDBBQAAAAIAGFiV1kAAA...",
"application_artkey": 35
}'
Request (Direct file upload)
curl -X POST "https://httpapi.sustainder.com/v2/system-import" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" \
--data-binary @device-import.xlsx
Response (200 OK)
{
"message": "Import completed successfully.",
"imported": 25,
"skipped": 2,
"errors": [
{
"row": 8,
"field": "lcm_id",
"message": "LCM ID 'LCM-009999' is already registered in the system."
},
{
"row": 17,
"field": "latitude",
"message": "Invalid latitude value '999.123'. Must be between -90 and 90."
}
],
"warnings": [
{
"row": 3,
"field": "pole_number",
"message": "Pole number 'P-042' is already assigned to another device. Device imported without pole number."
}
]
}
Imports devices into the system from a structured file. The file can be uploaded either as a direct binary upload or as a Base64-encoded string in a JSON body. Devices with validation errors are skipped, and the remaining valid devices are imported.
Content Types
| Content-Type | Description |
|---|---|
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
Direct Excel file upload (binary). |
application/json |
JSON body with file_base64 field containing the Base64-encoded file. |
Request Parameters (JSON)
| Parameter | Type | Required | Description |
|---|---|---|---|
| file_base64 | string | Yes | Base64-encoded import file content. |
| application_artkey | integer | Yes | The application to import devices into. |
Response Fields
| Field | Type | Description |
|---|---|---|
| message | string | Summary of the import operation. |
| imported | integer | Number of devices successfully imported. |
| skipped | integer | Number of devices skipped due to errors. |
| errors | array | List of row-level errors encountered during import. |
| warnings | array | List of row-level warnings (device was imported but with modifications). |
Error Object
| Field | Type | Description |
|---|---|---|
| row | integer | The row number in the file where the error occurred. |
| field | string | The field that caused the error. |
| message | string | Description of the validation error. |
Warning Object
| Field | Type | Description |
|---|---|---|
| row | integer | The row number in the file where the warning was generated. |
| field | string | The field that triggered the warning. |
| message | string | Description of the issue and how it was handled. |
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid file format or empty file. |
| 422 | All rows failed validation. No devices were imported. |
/system-import/validate [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/system-import/validate" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"file_base64": "UEsDBBQAAAAIAGFiV1kAAA...",
"application_artkey": 35
}'
Request body
{
"file_base64": "UEsDBBQAAAAIAGFiV1kAAA...",
"application_artkey": 35
}
Response (200 OK) -- Validation passed
{
"valid": true,
"message": "Validation passed. 25 devices ready for import.",
"total_rows": 25,
"valid_rows": 25,
"invalid_rows": 0,
"errors": [],
"warnings": []
}
Response (200 OK) -- Validation with issues
{
"valid": false,
"message": "Validation completed with errors. 23 of 27 devices are valid.",
"total_rows": 27,
"valid_rows": 23,
"invalid_rows": 4,
"errors": [
{
"row": 5,
"field": "lcm_id",
"message": "Missing required field 'lcm_id'."
},
{
"row": 12,
"field": "model",
"message": "Unknown model 'unknown_model'. Supported: alexia, anne, bianca."
},
{
"row": 19,
"field": "longitude",
"message": "Invalid longitude value '200.5'. Must be between -180 and 180."
},
{
"row": 22,
"field": "lcm_id",
"message": "Duplicate LCM ID 'LCM-004567' found in rows 22 and 3."
}
],
"warnings": [
{
"row": 7,
"field": "pole_number",
"message": "Pole number 'P-055' is already assigned to device LCM-001100. It will be reassigned on import."
},
{
"row": 15,
"field": "area",
"message": "Area 'Nieuw-West' does not match any existing area. A new area will be created."
}
]
}
Validates an import file without actually importing any devices. Use this endpoint to check for errors and warnings before committing to a full import. The response indicates whether the file is valid and provides details about any issues found.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| file_base64 | string | Yes | Base64-encoded import file content. |
| application_artkey | integer | Yes | The application to validate against. |
Response Fields
| Field | Type | Description |
|---|---|---|
| valid | boolean | true if all rows passed validation, false if any errors were found. |
| message | string | Human-readable summary of the validation result. |
| total_rows | integer | Total number of data rows found in the file. |
| valid_rows | integer | Number of rows that passed validation. |
| invalid_rows | integer | Number of rows that failed validation. |
| errors | array | List of validation errors. Same structure as import errors. |
| warnings | array | List of validation warnings. Same structure as import warnings. |
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid file format or empty file. |
Users
Endpoints for listing and viewing user information within an application.
/users [GET]
Request
curl "https://httpapi.sustainder.com/v2/users" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/users"
}
},
"_embedded": {
"users": [
{
"artkey": 101,
"username": "john.doe@example.com",
"role": "ADMIN",
"is_active": true,
"last_login": "2024-11-15T09:30:00Z"
},
{
"artkey": 102,
"username": "jane.smith@example.com",
"role": "USER",
"is_active": true,
"last_login": "2024-11-14T16:45:00Z"
}
]
},
"_total": 2
}
Lists all users with access to the application.
Response Fields
| Field | Type | Description |
|---|---|---|
| artkey | integer | Unique user identifier. |
| username | string | The user's username/email. |
| role | string | User role: ADMIN or USER. |
| is_active | boolean | Whether the user account is active. |
| last_login | string | ISO 8601 timestamp of the user's last login. |
/users/{user_artkey} [GET]
Request
curl "https://httpapi.sustainder.com/v2/users/101" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"artkey": 101,
"username": "john.doe@example.com",
"role": "ADMIN",
"is_active": true,
"last_login": "2024-11-15T09:30:00Z",
"permission_change_dimming_scheme": true,
"permission_change_light_level": true,
"receive_email_report": true,
"receive_report_daily": false,
"receive_report_weekly": true,
"receive_report_monthly": true,
"allow_camera_portal": true,
"receive_motion_report_email": false
}
Returns detailed information for a specific user.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| user_artkey | integer | The user's artkey identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| artkey | integer | Unique user identifier. |
| username | string | The user's username/email. |
| role | string | User role: ADMIN or USER. |
| is_active | boolean | Whether the account is active. |
| last_login | string | ISO 8601 timestamp of last login. |
| permission_change_dimming_scheme | boolean | Can modify dimming schemes. |
| permission_change_light_level | boolean | Can modify light levels. |
| receive_email_report | boolean | Receives email reports. |
| receive_report_daily | boolean | Receives daily reports. |
| receive_report_weekly | boolean | Receives weekly reports. |
| receive_report_monthly | boolean | Receives monthly reports. |
| allow_camera_portal | boolean | Has access to the camera portal. |
| receive_motion_report_email | boolean | Receives motion detection reports. |
Error Responses
| Status | Description |
|---|---|
| 404 | User not found. |
Email Preferences
Manage email notification preferences for the authenticated user.
/email-preference [GET]
Request
curl "https://httpapi.sustainder.com/v2/email-preference" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"receive_email_report": true,
"receive_report_daily": false,
"receive_report_weekly": true,
"receive_report_monthly": true,
"receive_motion_report_email": false
}
Returns the current email notification preferences for the authenticated user.
Response Fields
| Field | Type | Description |
|---|---|---|
| receive_email_report | boolean | Master toggle for email reports. |
| receive_report_daily | boolean | Receive daily summary reports. |
| receive_report_weekly | boolean | Receive weekly summary reports. |
| receive_report_monthly | boolean | Receive monthly summary reports. |
| receive_motion_report_email | boolean | Receive motion detection event reports. |
/email-preference [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/email-preference" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"receive_email_report": true,
"receive_report_daily": true,
"receive_report_weekly": true,
"receive_report_monthly": false,
"receive_motion_report_email": true
}'
Request body
{
"receive_email_report": true,
"receive_report_daily": true,
"receive_report_weekly": true,
"receive_report_monthly": false,
"receive_motion_report_email": true
}
Response (200 OK)
{
"message": "Email preferences updated successfully."
}
Updates email notification preferences for the authenticated user.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| receive_email_report | boolean | No | Master toggle for email reports. |
| receive_report_daily | boolean | No | Receive daily summary reports. |
| receive_report_weekly | boolean | No | Receive weekly summary reports. |
| receive_report_monthly | boolean | No | Receive monthly summary reports. |
| receive_motion_report_email | boolean | No | Receive motion detection event reports. |
Terms of Service
Manage terms of service acceptance for users.
/tos [GET]
Request
curl "https://httpapi.sustainder.com/v2/tos" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"tos_accepted": true,
"tos_version": "2.1",
"accepted_at": "2024-10-01T10:00:00Z"
}
Returns the current terms of service acceptance status for the authenticated user.
Response Fields
| Field | Type | Description |
|---|---|---|
| tos_accepted | boolean | Whether the user has accepted the current ToS. |
| tos_version | string | Version of the terms of service. |
| accepted_at | string\ | null |
/tos [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/tos" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"accepted": true
}'
Request body
{
"accepted": true
}
Response (200 OK)
{
"message": "Terms of service accepted."
}
Accepts the current terms of service for the authenticated user.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| accepted | boolean | Yes | Must be true to accept the ToS. |
/tos/skip [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/tos/skip" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"message": "Terms of service skipped."
}
Skips the terms of service prompt for the current session. The user will be prompted again on next login.
Dashboard
Dashboard endpoints provide summary data for the application overview.
/dashboard/alarms [GET]
Request
curl "https://httpapi.sustainder.com/v2/dashboard/alarms?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"alarms": {
"total": 12,
"by_type": {
"LAMP": 5,
"BALLAST": 2,
"LCM_NOT_RESPONDING": 3,
"TILTED_WARNING": 1,
"GATEWAY_NOT_RESPONDING": 1
}
}
}
Returns a summary of active alarms/errors for the application dashboard.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
/dashboard/updates [GET]
Request
curl "https://httpapi.sustainder.com/v2/dashboard/updates?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"updates": {
"pending_settings": 8,
"recent_status_updates": 142,
"last_gateway_check": "2024-11-15T14:00:00Z"
}
}
Returns recent system update information for the application dashboard.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
/dashboard/devices [GET]
Request
curl "https://httpapi.sustainder.com/v2/dashboard/devices?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"devices": {
"total": 256,
"online": 248,
"offline": 8,
"with_errors": 12,
"gateways_total": 3,
"gateways_online": 2,
"gateways_offline": 1
}
}
Returns a summary of device status counts for the application dashboard.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| total | integer | Total number of devices. |
| online | integer | Number of devices currently online. |
| offline | integer | Number of devices currently offline. |
| with_errors | integer | Number of devices with active errors. |
| gateways_total | integer | Total number of gateways. |
| gateways_online | integer | Number of gateways currently online. |
| gateways_offline | integer | Number of gateways currently offline. |
Application
Application-level configuration endpoints.
/app/icon [GET]
Request
curl "https://httpapi.sustainder.com/v2/app/icon?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"icon": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"content_type": "image/png"
}
Returns the application icon as a base64-encoded image.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| icon | string\ | null |
| content_type | string | MIME type of the image (e.g., image/png). |
/app/heatmap-intensity [GET]
Request
curl "https://httpapi.sustainder.com/v2/app/heatmap-intensity?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"heatmap_intensity": 0.75,
"min_intensity": 0.1,
"max_intensity": 1.0
}
Returns the configured heatmap intensity for the application's motion data visualization.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| heatmap_intensity | float | Current heatmap intensity value (0.0 - 1.0). |
| min_intensity | float | Minimum allowed intensity. |
| max_intensity | float | Maximum allowed intensity. |
Options
Reference data endpoints for device configuration options.
/options/optics [GET]
Request
curl "https://httpapi.sustainder.com/v2/options/optics" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"optics": [
"Wide Street",
"Medium Street",
"Narrow Street",
"Residential",
"Parking",
"Pedestrian",
"Cycle Path",
"Urban",
"Flood Wide",
"Flood Narrow"
]
}
Returns the list of available optics options for device configuration.
Response Fields
| Field | Type | Description |
|---|---|---|
| optics | array | List of available optics type names. |
/options/ccts [GET]
Request
curl "https://httpapi.sustainder.com/v2/options/ccts" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"ccts": [
"2200K",
"2700K",
"3000K",
"3500K",
"4000K",
"5000K",
"5700K",
"Tunable White"
]
}
Returns the list of available CCT (Correlated Color Temperature) options for device configuration.
Response Fields
| Field | Type | Description |
|---|---|---|
| ccts | array | List of available CCT values/names. |
Coordinate Systems
/coordinate-systems [GET]
Request
curl "https://httpapi.sustainder.com/v2/coordinate-systems" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"coordinate_systems": [
{
"name": "WGS84",
"description": "World Geodetic System 1984 (GPS standard)",
"epsg": 4326
},
{
"name": "RD New",
"description": "Rijksdriehoeksstelsel (Dutch national grid)",
"epsg": 28992
}
]
}
Returns the list of supported coordinate systems for device location data. These are used when importing devices to specify which coordinate system the provided coordinates are in.
Response Fields
| Field | Type | Description |
|---|---|---|
| name | string | Short name of the coordinate system. |
| description | string | Human-readable description. |
| epsg | integer | EPSG code for the coordinate reference system. |
Feature Flags
/feature-flags [GET]
Request
curl "https://httpapi.sustainder.com/v2/feature-flags" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"feature_flags": {
"motion_detection": true,
"camera_portal": true,
"building_management": false,
"energy_dashboard": true,
"asset_management": true,
"custom_fields": true,
"node_replacement": true,
"dummy_devices": true,
"alarm_logbook": true,
"heatmap": true,
"jira_integration": false,
"layers": false
}
}
Returns the feature flags for the current application. Feature flags control which features are enabled or available in the user interface and API.
Response Fields
| Field | Type | Description |
|---|---|---|
| feature_flags | object | Key-value pairs where keys are feature names and values are booleans indicating if the feature is enabled. |
Common Feature Flags
| Flag | Description |
|---|---|
| motion_detection | Motion-based lighting control. |
| camera_portal | Camera viewing and management. |
| building_management | Indoor building lighting control. |
| energy_dashboard | Energy consumption dashboards. |
| asset_management | Device asset management features. |
| custom_fields | Custom metadata fields on devices. |
| node_replacement | Node replacement workflow. |
| dummy_devices | Placeholder device planning. |
| alarm_logbook | Historical alarm event logging. |
| heatmap | Motion data heatmap visualization. |
| jira_integration | Jira ticket integration. |
| layers | Data layer management. |
Releases
Manage release notes and announcements.
/releases [GET]
Request
curl "https://httpapi.sustainder.com/v2/releases" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"releases": [
{
"id": 15,
"title": "Motion Detection Improvements",
"content": "Improved motion detection algorithm with better sensitivity controls and reduced false positives.",
"version": "3.2.0",
"created_at": "2024-11-01T10:00:00Z",
"is_read": true
},
{
"id": 14,
"title": "Energy Dashboard Update",
"content": "New energy consumption graphs with daily, weekly, and monthly comparisons.",
"version": "3.1.0",
"created_at": "2024-10-15T10:00:00Z",
"is_read": false
}
]
}
Lists all release notes, ordered by most recent first.
Response Fields
| Field | Type | Description |
|---|---|---|
| id | integer | Unique release identifier. |
| title | string | Release title. |
| content | string | Release description/notes. |
| version | string | Software version number. |
| created_at | string | ISO 8601 timestamp of when the release was published. |
| is_read | boolean | Whether the authenticated user has marked this release as read. |
/releases [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/releases" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"title": "New Feature: Layers",
"content": "Introducing data layers for customizable map overlays.",
"version": "3.3.0"
}'
Request body
{
"title": "New Feature: Layers",
"content": "Introducing data layers for customizable map overlays.",
"version": "3.3.0"
}
Response (201 Created)
{
"id": 16,
"title": "New Feature: Layers",
"content": "Introducing data layers for customizable map overlays.",
"version": "3.3.0",
"created_at": "2024-11-15T15:00:00Z"
}
Creates a new release note.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| title | string | Yes | Release title. |
| content | string | Yes | Release description/notes. |
| version | string | Yes | Software version number. |
/releases/read [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/releases/read" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"release_ids": [14, 15]
}'
Request body
{
"release_ids": [14, 15]
}
Response (200 OK)
{
"message": "Releases marked as read."
}
Marks one or more releases as read for the authenticated user.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| release_ids | array | Yes | List of release IDs to mark as read. |
FAQ & Feedback
/faq [GET]
Request
curl "https://httpapi.sustainder.com/v2/faq" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"faq": [
{
"question": "How do I reset a device error?",
"answer": "Navigate to the device details page and use the 'Reset Error' button, or call the POST /devices/{lcm_id}/reset-error endpoint.",
"category": "Troubleshooting"
},
{
"question": "What is the maximum duration for a direct control override?",
"answer": "The maximum duration is 240 minutes (4 hours). If no duration is specified, the override persists until cleared.",
"category": "Lighting Control"
}
]
}
Returns frequently asked questions and answers.
Response Fields
| Field | Type | Description |
|---|---|---|
| question | string | The FAQ question. |
| answer | string | The answer text. |
| category | string | Category grouping for the FAQ item. |
/feedbacks/malfunction [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/feedbacks/malfunction" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"lcm_id": "LCM-001234",
"description": "Light is flickering intermittently during evening hours.",
"application_artkey": 35
}'
Request body
{
"lcm_id": "LCM-001234",
"description": "Light is flickering intermittently during evening hours.",
"application_artkey": 35
}
Response (201 Created)
{
"message": "Malfunction feedback submitted successfully.",
"feedback_id": 42
}
Submits a malfunction report for a specific device.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| lcm_id | string | Yes | The LCM identifier experiencing the issue. |
| description | string | Yes | Description of the malfunction. |
| application_artkey | integer | Yes | The application identifier. |
Webhooks
Webhooks allow you to receive real-time notifications when events occur in the SBL. You subscribe to specific event types by providing a URL where the SBL will send HTTP POST requests with event data.
/webhooks [GET]
Request
curl "https://httpapi.sustainder.com/v2/webhooks" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/webhooks"
},
"dimming_scheme": {
"href": "https://httpapi.sustainder.com/v2/webhooks/dimming_scheme"
},
"status_response": {
"href": "https://httpapi.sustainder.com/v2/webhooks/status_response"
},
"status_push": {
"href": "https://httpapi.sustainder.com/v2/webhooks/status_push"
},
"gateway_state_change": {
"href": "https://httpapi.sustainder.com/v2/webhooks/gateway_state_change"
}
}
}
Lists all available webhook event types you can subscribe to.
Available Event Types
| Event Type | Description |
|---|---|
dimming_scheme |
Triggered when a dimming scheme change is confirmed on a device. |
status_response |
Triggered when a device responds to a status request. |
status_push |
Triggered when a device sends an unsolicited status update. |
gateway_state_change |
Triggered when a gateway goes online or offline. |
/webhooks/{event_type} [GET]
Request
curl "https://httpapi.sustainder.com/v2/webhooks/dimming_scheme" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"_links": {
"self": {
"href": "https://httpapi.sustainder.com/v2/webhooks/dimming_scheme"
}
},
"event_type": "dimming_scheme",
"remote_url": "https://mycompany.com/sustainder/dimming_scheme",
"auth_method": "BASIC",
"username": "webhook-user",
"is_active": true
}
Returns the current webhook configuration for a specific event type.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| event_type | string | The webhook event type (e.g., dimming_scheme, status_response). |
Response Fields
| Field | Type | Description |
|---|---|---|
| event_type | string | The event type this webhook subscribes to. |
| remote_url | string | The URL where events are sent. |
| auth_method | string | Authentication method: BASIC, BEARER, or NONE. |
| username | string | Username for Basic auth (if applicable). |
| is_active | boolean | Whether the webhook is currently active. |
/webhooks/{event_type} [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/webhooks/status_push" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"remote_url": "https://mycompany.com/sustainder/status-updates",
"auth_method": "BASIC",
"username": "webhook-user",
"password_or_token": "webhook-secret-password",
"application_artkey": 35
}'
Request body
{
"remote_url": "https://mycompany.com/sustainder/status-updates",
"auth_method": "BASIC",
"username": "webhook-user",
"password_or_token": "webhook-secret-password",
"application_artkey": 35
}
Response (201 Created)
{
"message": "Webhook created successfully.",
"event_type": "status_push",
"remote_url": "https://mycompany.com/sustainder/status-updates"
}
Creates or updates a webhook subscription for an event type.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| event_type | string | The webhook event type to subscribe to. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| remote_url | string | Yes | The URL to send webhook events to (must be HTTPS). |
| auth_method | string | Yes | Authentication method: BASIC, BEARER, or NONE. |
| username | string | Conditional | Required when auth_method is BASIC. |
| password_or_token | string | Conditional | Password for BASIC auth or token for BEARER auth. |
| application_artkey | integer | Yes | The application identifier. |
Webhook Payload Format
Example webhook payload (status_push)
{
"event_type": "status_push",
"timestamp": "2024-11-15T14:32:08Z",
"data": {
"lcm_id": "LCM-001234",
"status": "ONLINE",
"lighting_mode": "DIMSCHEME",
"power_watt": 42.5,
"energy_kwh": 1247.3,
"temperature_internal": 18.5
}
}
When an event occurs, the SBL sends an HTTP POST request to your configured URL with a JSON payload containing the event type, timestamp, and event-specific data.
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid URL or authentication configuration. |
Energy Data
Endpoints for retrieving energy consumption data and statistics.
/energy-data [GET]
Request
curl "https://httpapi.sustainder.com/v2/energy-data?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"energy_data": [
{
"lcm_id": "LCM-001234",
"node_name": "Keizersgracht 42",
"energy_kwh": 1247.3,
"power_watt": 42.5,
"running_hours": 12450,
"last_updated": "2024-11-15T14:00:00Z"
},
{
"lcm_id": "LCM-001235",
"node_name": "Herengracht 108",
"energy_kwh": 985.1,
"power_watt": 38.2,
"running_hours": 11200,
"last_updated": "2024-11-15T13:45:00Z"
}
],
"_total": 128
}
Returns energy consumption data for all devices in the application.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
| node_name | string | Display name of the node. |
| energy_kwh | float | Cumulative energy consumption in kWh. |
| power_watt | float | Current power draw in Watts. |
| running_hours | integer | Total running hours. |
| last_updated | string | ISO 8601 timestamp of the last energy reading. |
/energy-stats [GET]
Request
curl "https://httpapi.sustainder.com/v2/energy-stats?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"total_energy_kwh": 159654.2,
"total_power_watt": 5440.0,
"average_power_watt": 42.5,
"device_count": 128,
"period_start": "2024-01-01T00:00:00Z",
"period_end": "2024-11-15T23:59:59Z"
}
Returns aggregated energy statistics for the application.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| total_energy_kwh | float | Total cumulative energy consumption across all devices. |
| total_power_watt | float | Sum of current power draw across all devices. |
| average_power_watt | float | Average power draw per device. |
| device_count | integer | Number of devices included in the statistics. |
| period_start | string | Start of the measurement period. |
| period_end | string | End of the measurement period. |
/recent-energy-stats [GET]
Request
curl "https://httpapi.sustainder.com/v2/recent-energy-stats?application_artkey=35" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"recent_stats": {
"last_24h_kwh": 52.3,
"last_7d_kwh": 365.8,
"last_30d_kwh": 1542.1,
"average_daily_kwh": 51.4
}
}
Returns recent energy consumption statistics.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| application_artkey | integer | Yes | The application identifier. |
/lcms/{lcm_id}/energy-data [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234/energy-data" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"lcm_id": "LCM-001234",
"node_name": "Keizersgracht 42",
"energy_kwh": 1247.3,
"power_watt": 42.5,
"running_hours": 12450,
"last_updated": "2024-11-15T14:00:00Z",
"history": [
{
"timestamp": "2024-11-15T00:00:00Z",
"energy_kwh": 1245.8,
"power_watt": 41.2
},
{
"timestamp": "2024-11-14T00:00:00Z",
"energy_kwh": 1244.3,
"power_watt": 43.1
}
]
}
Returns energy consumption data for a specific LCM, including recent history.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
Error Responses
| Status | Description |
|---|---|
| 404 | LCM not found. |
Jira Integration
Endpoints for viewing Jira tickets linked to devices and receiving Jira webhook events.
/lcms/{lcm_id}/jira-tickets [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/LCM-001234/jira-tickets" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"device_id": "LCM-001234",
"jira_tickets": [
{
"ticket_id": "SBL-1234",
"status": "In Progress",
"summary": "Replace damaged lamp on Keizersgracht 42",
"link": "https://sustainder.atlassian.net/browse/SBL-1234"
},
{
"ticket_id": "SBL-1180",
"status": "Done",
"summary": "Investigate tilted warning on pole P-042",
"link": "https://sustainder.atlassian.net/browse/SBL-1180"
}
],
"count": 2
}
Returns all Jira tickets associated with a specific device.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| lcm_id | string | The LCM identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| device_id | string | The LCM identifier. |
| jira_tickets | array | List of linked Jira tickets. |
| jira_tickets[].ticket_id | string | Jira issue key (e.g., SBL-1234). |
| jira_tickets[].status | string | Current Jira ticket status. |
| jira_tickets[].summary | string | Ticket summary/title. |
| jira_tickets[].link | string | Direct URL to the Jira ticket. |
| count | integer | Total number of linked tickets. |
Error Responses
| Status | Description |
|---|---|
| 404 | Device not found. |
/jira/webhook [POST]
Request
curl -X POST "https://httpapi.sustainder.com/v2/jira/webhook" \
-H "Content-Type: application/json" \
-H "X-Hub-Signature: sha256=abc123..." \
-d '{
"webhookEvent": "jira:issue_updated",
"issue": {
"key": "SBL-1234",
"fields": {
"status": { "name": "Done" },
"summary": "Replace damaged lamp on Keizersgracht 42"
}
}
}'
Response (200 OK)
200 OK
Receives incoming Jira webhook events. This endpoint is called by Jira when tickets are created or updated. It does not require JWT authentication but validates requests using HMAC-SHA256 signature verification.
Supported Event Types
| Event | Description |
|---|---|
jira:issue_created |
A new Jira issue has been created. |
jira:issue_updated |
An existing Jira issue has been updated. |
Error Responses
| Status | Description |
|---|---|
| 401 | HMAC signature mismatch — request not from authorized Jira instance. |
Product Passport
/lcms/product-passport [GET]
Request
curl "https://httpapi.sustainder.com/v2/lcms/product-passport?lcm_id=LCM-001234"
Response (200 OK)
{
"device_udid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"project_name": "Amsterdam Centrum",
"application_name": "Amsterdam Centrum",
"model": "alexia",
"model_type": "streetlight",
"device_production_details": {
"production_date": "2023-03-15",
"driver_type": "DALI-2",
"lumen_output": 4500,
"watt": 45,
"armature_color": "RAL 7016",
"light_color": "3000K",
"clo": "enabled",
"optics": "Wide Street",
"dimming_scheme": "Standard Evening",
"mounting_diameter": "60mm",
"guard": "none",
"tilt_angle": "0",
"cable_type": "5G2.5",
"cable_length": "1.5m",
"optic_addon": null
}
}
Returns the product passport (manufacturing and specification details) for an LCM. This endpoint is publicly accessible without authentication.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| lcm_id | string | Yes | The LCM identifier. |
Response Fields
| Field | Type | Description |
|---|---|---|
| device_udid | string | Universal device unique identifier (UUID). |
| project_name | string\ | null |
| application_name | string\ | null |
| model | string | Device model name (e.g., alexia, anne). |
| model_type | string | Device model type (e.g., streetlight). |
| device_production_details | object | Manufacturing and specification details. |
| device_production_details.production_date | string\ | null |
| device_production_details.driver_type | string | LED driver type. |
| device_production_details.lumen_output | number | Luminaire output in lumens. |
| device_production_details.watt | number | Rated wattage. |
| device_production_details.armature_color | string | Armature color code. |
| device_production_details.light_color | string | Color temperature. |
| device_production_details.clo | string | Constant Light Output status. |
| device_production_details.optics | string | Installed optics type. |
| device_production_details.dimming_scheme | string | Factory dimming scheme. |
| device_production_details.mounting_diameter | string | Post mounting diameter. |
| device_production_details.guard | string | Guard type. |
| device_production_details.tilt_angle | string | Factory tilt angle. |
| device_production_details.cable_type | string | Cable specification. |
| device_production_details.cable_length | string | Cable length. |
| device_production_details.optic_addon | string\ | null |
Error Responses
| Status | Description |
|---|---|
| 404 | LCM not found. |
Geolocation
Geocoding endpoints for converting between addresses and GPS coordinates.
/geo/coordinates [GET]
Request
curl "https://httpapi.sustainder.com/v2/geo/coordinates?address=Keizersgracht%2042%2C%20Amsterdam" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"results": [
{
"formatted_address": "Keizersgracht 42, 1015 CR Amsterdam, Netherlands",
"latitude": 52.3702,
"longitude": 4.8879
}
]
}
Looks up GPS coordinates from a street address using Google Maps geocoding.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| address | string | Yes | The address to geocode (URL-encoded). |
Response Fields
| Field | Type | Description |
|---|---|---|
| results | array | List of matching locations. |
| results[].formatted_address | string | Full formatted address from Google Maps. |
| results[].latitude | float | GPS latitude coordinate. |
| results[].longitude | float | GPS longitude coordinate. |
/geo/addresses [GET]
Request
curl "https://httpapi.sustainder.com/v2/geo/addresses?latitude=52.3702&longitude=4.8879" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"results": [
{
"formatted_address": "Keizersgracht 42, 1015 CR Amsterdam, Netherlands",
"components": {
"street_number": "42",
"route": "Keizersgracht",
"locality": "Amsterdam",
"postal_code": "1015 CR",
"country": "Netherlands"
}
}
]
}
Performs reverse geocoding — converts GPS coordinates to a street address.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| latitude | float | Yes | GPS latitude coordinate. |
| longitude | float | Yes | GPS longitude coordinate. |
Response Fields
| Field | Type | Description |
|---|---|---|
| results | array | List of address results. |
| results[].formatted_address | string | Full formatted address. |
| results[].components | object | Parsed address components. |
| results[].components.street_number | string | Street number. |
| results[].components.route | string | Street name. |
| results[].components.locality | string | City/town. |
| results[].components.postal_code | string | Postal/ZIP code. |
| results[].components.country | string | Country name. |
System
System-level endpoints for health checks and API information. These endpoints do not require authentication.
/ [GET]
Request
curl "https://httpapi.sustainder.com/"
Response (200 OK)
{
"message": "Welcome to the API of the Sustainder Brokerage Layer! Documentation can be found at https://apidocs.sustainder.com/."
}
The root endpoint. Returns a welcome message with a link to the API documentation.
/status [GET]
Request
curl "https://httpapi.sustainder.com/status"
Response (200 OK)
OK
Health check endpoint. Returns OK if the API is running and healthy.
/version [GET]
Request
curl "https://httpapi.sustainder.com/version"
Response (200 OK)
{
"version": "b3b71eb"
}
Returns the current API version (git commit hash).
Response Fields
| Field | Type | Description |
|---|---|---|
| version | string | Git commit hash of the deployed version. |
/ping [GET]
Request
curl "https://httpapi.sustainder.com/ping"
Response (200 OK)
{}
Simple ping endpoint. Returns an empty JSON object to confirm the API is reachable.
Admin API
The Admin API provides administrative endpoints for bulk operations, gateway management, and system maintenance. These endpoints require admin-level authentication.
Base URL: https://httpapi.sustainder.com/admin
Admin Authentication
Admin endpoints use separate JWT authentication via /admin/auth/jwt. Regular user tokens do not work with admin endpoints.
Resources Overview
Authentication
| Method | Endpoint | Description |
|---|---|---|
| POST | /admin/auth/jwt | Authenticate as admin and obtain JWT token |
| GET | /admin/me | Get current admin user info |
Gateways
| Method | Endpoint | Description |
|---|---|---|
| GET | /admin/gateways/online | List online gateways |
| POST | /admin/gateways/messages | Get gateway messages |
| POST | /admin/gateways/{gateway_id}/send | Send payload to gateway via MQTT |
| POST | /admin/gateways/{gateway_id}/do_daily_scan | Request gateway daily scan |
| PATCH | /admin/gateways | Bulk update gateways |
| POST | /admin/gateways/{gateway_id}/transparency/enable | Enable transparency mode |
| POST | /admin/gateways/{gateway_id}/transparency/disable | Disable transparency mode |
| POST | /admin/gateways/{gateway_id}/scan | Scan for LCMs in range |
| POST | /admin/gateways/{gateway_id}/cancel | Cancel pending gateway command |
Devices
| Method | Endpoint | Description |
|---|---|---|
| PATCH | /admin/devices | Bulk update devices |
| POST | /admin/devices/replace | Replace device nodes |
Users
| Method | Endpoint | Description |
|---|---|---|
| PATCH | /admin/users | Bulk update users |
Admin Auth
This section covers admin authentication endpoints. Admin authentication is separate from the regular user authentication system and provides elevated privileges for system management.
/admin/auth/jwt [POST]
Request
curl -X POST "https://httpapi.sustainder.com/admin/auth/jwt?username=admin@sustainder.com&password=Adm1nS3cur3!" \
-H "Content-Type: application/json"
Response (200 OK)
{
"name": "admin@sustainder.com",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789",
"auth-header": "JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789"
}
Authenticates an admin user with username and password to obtain a JWT token. Credentials are passed as query parameters. The returned auth-header field contains the complete value for the HTTP Authorization header used in subsequent admin API requests.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| username | string | Yes | The admin user's email address or username. |
| password | string | Yes | The admin user's password. |
Response Fields
| Field | Type | Description |
|---|---|---|
| name | string | The authenticated admin username. |
| token | string | The JWT token value. |
| auth-header | string | The complete Authorization header value (JWT <token>). |
Error Responses
| Status | Description |
|---|---|
| 401 | Invalid credentials. The username or password is incorrect. |
/admin/me [GET]
Request
curl "https://httpapi.sustainder.com/admin/me" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789"
Response (200 OK)
{
"name": "admin@sustainder.com"
}
Returns basic identity information for the currently authenticated admin user. This endpoint can be used to verify that the admin JWT token is valid and to retrieve the admin username.
Response Fields
| Field | Type | Description |
|---|---|---|
| name | string | The authenticated admin user's username. |
Error Responses
| Status | Description |
|---|---|
| 401 | Not authenticated or admin token expired. |
| 403 | Token is not an admin token. Regular user tokens are not accepted. |
Admin Gateways
Administrative endpoints for managing gateways, including monitoring online status, sending MQTT commands, bulk updates, and transparency mode control.
/admin/gateways/online [GET]
Request
curl "https://httpapi.sustainder.com/admin/gateways/online" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789"
Response (200 OK)
{
"gateways": [
"352890061121289",
"352890061121345",
"352890061121567",
"352890061121890"
]
}
Returns a list of all gateway IDs that are currently online and connected to the system. This provides a quick overview of gateway connectivity across all applications.
Response Fields
| Field | Type | Description |
|---|---|---|
| gateways | array | List of gateway ID strings that are currently online. |
Error Responses
| Status | Description |
|---|---|
| 401 | Not authenticated or admin token expired. |
| 403 | Insufficient admin privileges. |
/admin/gateways/messages [POST]
Request
curl -X POST "https://httpapi.sustainder.com/admin/gateways/messages?offset=0&limit=20&gateway_ids=352890061121289,352890061121345&start_date=2024-11-01T00:00:00Z&end_date=2024-11-15T23:59:59Z" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789"
Response (200 OK)
{
"messages": [
{
"gateway_id": "352890061121289",
"timestamp": "2024-11-15T14:32:10Z",
"direction": "inbound",
"topic": "gw/352890061121289/status",
"payload": "{\"status\":\"online\",\"uptime\":86400}"
},
{
"gateway_id": "352890061121289",
"timestamp": "2024-11-15T14:30:00Z",
"direction": "outbound",
"topic": "gw/352890061121289/command",
"payload": "{\"command\":\"daily_scan\"}"
},
{
"gateway_id": "352890061121345",
"timestamp": "2024-11-15T14:28:45Z",
"direction": "inbound",
"topic": "gw/352890061121345/status",
"payload": "{\"status\":\"online\",\"uptime\":43200}"
}
],
"total": 156,
"offset": 0,
"limit": 20
}
Retrieves MQTT messages for one or more gateways within a specified time range. Results are paginated using offset and limit parameters.
Query Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| offset | integer | No | 0 | Number of records to skip for pagination. |
| limit | integer | No | 20 | Maximum number of records to return. |
| gateway_ids | string | No | - | Comma-separated list of gateway IDs to filter by. If omitted, returns messages for all gateways. |
| start_date | string | No | - | ISO 8601 start date for the query range. |
| end_date | string | No | - | ISO 8601 end date for the query range. |
Response Fields
| Field | Type | Description |
|---|---|---|
| messages | array | List of gateway message objects. |
| messages[].gateway_id | string | The gateway that sent or received the message. |
| messages[].timestamp | string | ISO 8601 timestamp of the message. |
| messages[].direction | string | Message direction: inbound (from gateway) or outbound (to gateway). |
| messages[].topic | string | The MQTT topic of the message. |
| messages[].payload | string | The message payload (JSON-encoded string). |
| total | integer | Total number of matching messages. |
| offset | integer | Current pagination offset. |
| limit | integer | Current pagination limit. |
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid query parameters (e.g., malformed date format). |
| 401 | Not authenticated or admin token expired. |
| 403 | Insufficient admin privileges. |
/admin/gateways/{gateway_id}/send [POST]
Request
curl -X POST "https://httpapi.sustainder.com/admin/gateways/352890061121289/send" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789" \
-H "Content-Type: application/json" \
-d '{
"payload": {
"command": "reboot",
"priority": "normal"
}
}'
Request body
{
"payload": {
"command": "reboot",
"priority": "normal"
}
}
Response (200 OK)
{
"message": "Payload sent to gateway 352890061121289.",
"gateway_id": "352890061121289",
"status": "delivered"
}
Sends a custom payload to a specific gateway via MQTT. This allows administrators to issue arbitrary commands to gateways for debugging, maintenance, or manual control.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| gateway_id | string | The gateway identifier (typically an IMEI number). |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| payload | object | Yes | The JSON payload to send to the gateway via MQTT. Structure depends on the intended command. |
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid or missing payload. |
| 401 | Not authenticated or admin token expired. |
| 404 | Gateway not found. |
| 503 | Gateway is offline or unreachable. |
/admin/gateways/{gateway_id}/do_daily_scan [POST]
Request
curl -X POST "https://httpapi.sustainder.com/admin/gateways/352890061121289/do_daily_scan" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789"
Response (200 OK)
{
"message": "Daily scan requested for gateway 352890061121289.",
"gateway_id": "352890061121289",
"status": "pending"
}
Requests the gateway to perform its daily scan immediately rather than waiting for the scheduled time. The daily scan collects status information from all connected LCMs.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| gateway_id | string | The gateway identifier. |
Error Responses
| Status | Description |
|---|---|
| 401 | Not authenticated or admin token expired. |
| 404 | Gateway not found. |
| 503 | Gateway is offline or unreachable. |
/admin/gateways [PATCH]
Request
curl -X PATCH "https://httpapi.sustainder.com/admin/gateways" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789" \
-H "Content-Type: application/json" \
-d '[
{
"gateway_id": "352890061121289",
"timezone": "Europe/Amsterdam"
},
{
"gateway_id": "352890061121345",
"timezone": "Europe/London"
}
]'
Request body
[
{
"gateway_id": "352890061121289",
"timezone": "Europe/Amsterdam"
},
{
"gateway_id": "352890061121345",
"timezone": "Europe/London"
}
]
Response (200 OK)
{
"message": "2 gateways updated successfully.",
"updated": 2,
"errors": []
}
Bulk update multiple gateways in a single request. The request body is an array of gateway objects with the fields to update.
Request Body (Array)
| Field | Type | Required | Description |
|---|---|---|---|
| gateway_id | string | Yes | The gateway identifier to update. |
| timezone | string | No | The timezone for the gateway (e.g., Europe/Amsterdam). |
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid request body or malformed gateway data. |
| 401 | Not authenticated or admin token expired. |
| 403 | Insufficient admin privileges. |
/admin/gateways/{gateway_id}/transparency/enable [POST]
Request
curl -X POST "https://httpapi.sustainder.com/admin/gateways/352890061121289/transparency/enable" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789"
Response (200 OK)
{
"message": "Transparency mode enabled for gateway 352890061121289.",
"gateway_id": "352890061121289",
"transparency_mode": true
}
Enables transparency mode on a gateway. When transparency mode is enabled, the gateway forwards all raw MQTT traffic for diagnostic purposes, allowing administrators to inspect the full communication between the gateway and its connected LCMs.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| gateway_id | string | The gateway identifier. |
Error Responses
| Status | Description |
|---|---|
| 401 | Not authenticated or admin token expired. |
| 404 | Gateway not found. |
| 503 | Gateway is offline or unreachable. |
/admin/gateways/{gateway_id}/transparency/disable [POST]
Request
curl -X POST "https://httpapi.sustainder.com/admin/gateways/352890061121289/transparency/disable" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789"
Response (200 OK)
{
"message": "Transparency mode disabled for gateway 352890061121289.",
"gateway_id": "352890061121289",
"transparency_mode": false
}
Disables transparency mode on a gateway, returning it to normal operation where only relevant messages are processed and forwarded.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| gateway_id | string | The gateway identifier. |
Error Responses
| Status | Description |
|---|---|
| 401 | Not authenticated or admin token expired. |
| 404 | Gateway not found. |
| 503 | Gateway is offline or unreachable. |
/admin/gateways/{gateway_id}/scan [POST]
Request
curl -X POST "https://httpapi.sustainder.com/admin/gateways/352890061121289/scan" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789"
Response (200 OK)
{
"message": "LCM scan initiated for gateway 352890061121289.",
"gateway_id": "352890061121289",
"status": "scanning"
}
Initiates a scan for LCMs (Lighting Control Modules) within the radio frequency range of the specified gateway. This is used to discover new or unregistered LCMs that are physically present in the gateway's coverage area.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| gateway_id | string | The gateway identifier. |
Error Responses
| Status | Description |
|---|---|
| 401 | Not authenticated or admin token expired. |
| 404 | Gateway not found. |
| 409 | A scan is already in progress for this gateway. |
| 503 | Gateway is offline or unreachable. |
/admin/gateways/{gateway_id}/cancel [POST]
Request
curl -X POST "https://httpapi.sustainder.com/admin/gateways/352890061121289/cancel" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789"
Response (200 OK)
{
"message": "Pending command cancelled for gateway 352890061121289.",
"gateway_id": "352890061121289",
"status": "cancelled"
}
Cancels any pending command for the specified gateway. This is useful when a previously issued command (such as a scan or daily scan) needs to be aborted before it completes.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| gateway_id | string | The gateway identifier. |
Error Responses
| Status | Description |
|---|---|
| 401 | Not authenticated or admin token expired. |
| 404 | Gateway not found or no pending command to cancel. |
Admin Devices
Administrative endpoints for bulk device management, including mass updates and device node replacements.
/admin/devices [PATCH]
Request
curl -X PATCH "https://httpapi.sustainder.com/admin/devices" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789" \
-H "Content-Type: application/json" \
-d '[
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"gateway_id": "352890061121289",
"desired_gateway_id": "352890061121345",
"timezone": "Europe/Amsterdam",
"name": "Streetlight A-101",
"device_model": "LCM-200",
"model_type": "outdoor",
"device_type": "led_driver",
"power_failure_check_enabled": true,
"uninstall_before_gateway_switch": false,
"has_camera": false,
"fixed_latitude": 52.3676,
"fixed_longitude": 4.9041,
"approved": true,
"is_deleted": false,
"pole_number": "P-2048",
"street": "Keizersgracht",
"area": "Centrum",
"project_id": 42
},
{
"uuid": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"gateway_id": "352890061121289",
"timezone": "Europe/Amsterdam",
"name": "Streetlight A-102",
"device_model": "LCM-200",
"model_type": "outdoor",
"device_type": "led_driver",
"power_failure_check_enabled": true,
"uninstall_before_gateway_switch": false,
"has_camera": true,
"fixed_latitude": 52.3680,
"fixed_longitude": 4.9050,
"approved": true,
"is_deleted": false,
"pole_number": "P-2049",
"street": "Keizersgracht",
"area": "Centrum",
"project_id": 42
}
]'
Request body
[
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"gateway_id": "352890061121289",
"desired_gateway_id": "352890061121345",
"timezone": "Europe/Amsterdam",
"name": "Streetlight A-101",
"device_model": "LCM-200",
"model_type": "outdoor",
"device_type": "led_driver",
"power_failure_check_enabled": true,
"uninstall_before_gateway_switch": false,
"has_camera": false,
"fixed_latitude": 52.3676,
"fixed_longitude": 4.9041,
"approved": true,
"is_deleted": false,
"pole_number": "P-2048",
"street": "Keizersgracht",
"area": "Centrum",
"project_id": 42
},
{
"uuid": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"gateway_id": "352890061121289",
"timezone": "Europe/Amsterdam",
"name": "Streetlight A-102",
"device_model": "LCM-200",
"model_type": "outdoor",
"device_type": "led_driver",
"power_failure_check_enabled": true,
"uninstall_before_gateway_switch": false,
"has_camera": true,
"fixed_latitude": 52.3680,
"fixed_longitude": 4.9050,
"approved": true,
"is_deleted": false,
"pole_number": "P-2049",
"street": "Keizersgracht",
"area": "Centrum",
"project_id": 42
}
]
Response (200 OK)
{
"message": "2 devices updated successfully.",
"updated": 2,
"errors": []
}
Response with partial failure (200 OK)
{
"message": "1 of 2 devices updated successfully.",
"updated": 1,
"errors": [
{
"uuid": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"error": "Device not found."
}
]
}
Bulk update multiple devices in a single request. The request body is an array of device objects. Each object must include the uuid field to identify the device, along with any fields to update. Only the provided fields are modified; omitted fields remain unchanged.
Request Body (Array of Objects)
| Field | Type | Required | Description |
|---|---|---|---|
| uuid | string | Yes | The unique device identifier (UUID). |
| gateway_id | string | No | The current gateway ID the device is connected to. |
| desired_gateway_id | string | No | The target gateway ID for a gateway migration. |
| timezone | string | No | The device timezone (e.g., Europe/Amsterdam). |
| name | string | No | A human-readable name for the device. |
| device_model | string | No | The hardware model of the device (e.g., LCM-200). |
| model_type | string | No | The model type classification (e.g., outdoor, indoor). |
| device_type | string | No | The device type (e.g., led_driver, sensor). |
| power_failure_check_enabled | boolean | No | Whether automatic power failure detection is enabled. |
| uninstall_before_gateway_switch | boolean | No | If true, the device is uninstalled from its current gateway before switching to the desired gateway. |
| has_camera | boolean | No | Whether the device has an integrated camera. |
| fixed_latitude | float | No | Fixed GPS latitude coordinate of the device. |
| fixed_longitude | float | No | Fixed GPS longitude coordinate of the device. |
| approved | boolean | No | Whether the device is approved for operation. |
| is_deleted | boolean | No | Whether the device is marked as deleted (soft delete). |
| pole_number | string | No | The pole or mounting point identifier. |
| street | string | No | The street name where the device is located. |
| area | string | No | The area or district where the device is located. |
| project_id | integer | No | The project ID this device belongs to. |
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid request body or malformed device data. |
| 401 | Not authenticated or admin token expired. |
| 403 | Insufficient admin privileges. |
/admin/devices/replace [POST]
Request
curl -X POST "https://httpapi.sustainder.com/admin/devices/replace" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789" \
-H "Content-Type: application/json" \
-d '{
"replacements": {
"a1b2c3d4-e5f6-7890-abcd-ef1234567890": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"b2c3d4e5-f6a7-8901-bcde-f12345678901": "d4e5f6a7-b8c9-0123-defa-234567890123"
}
}'
Request body
{
"replacements": {
"a1b2c3d4-e5f6-7890-abcd-ef1234567890": "c3d4e5f6-a7b8-9012-cdef-123456789012",
"b2c3d4e5-f6a7-8901-bcde-f12345678901": "d4e5f6a7-b8c9-0123-defa-234567890123"
}
}
Response (200 OK)
{
"message": "2 device nodes replaced successfully.",
"replaced": 2,
"errors": []
}
Response with partial failure (200 OK)
{
"message": "1 of 2 device nodes replaced successfully.",
"replaced": 1,
"errors": [
{
"old_uuid": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"new_uuid": "d4e5f6a7-b8c9-0123-defa-234567890123",
"error": "New device node not found or already assigned."
}
]
}
Replaces device nodes by mapping old device UUIDs to new device UUIDs. The replacement process transfers all configuration, settings, and historical associations from the old device to the new one. This is typically used during hardware replacement operations.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| replacements | object | Yes | A mapping of old device UUIDs (keys) to new device UUIDs (values). Each key-value pair represents one device replacement. |
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid request body, malformed UUIDs, or empty replacement mapping. |
| 401 | Not authenticated or admin token expired. |
| 403 | Insufficient admin privileges. |
| 404 | One or more old device UUIDs not found. |
| 409 | One or more new device UUIDs are already assigned to another device. |
Admin Users
Administrative endpoint for bulk user management, including updating permissions, report preferences, and role assignments.
/admin/users [PATCH]
Request
curl -X PATCH "https://httpapi.sustainder.com/admin/users" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbl9pZCI6MSwiZXhwIjoxNzMxNjgyNzI4fQ.AdminTokenValueHere123456789" \
-H "Content-Type: application/json" \
-d '[
{
"artkey": 101,
"permission_change_dimming_scheme": true,
"permission_change_light_level": true,
"receive_email_report": true,
"receive_report_daily": false,
"receive_report_weekly": true,
"receive_report_monthly": true,
"allow_camera_portal": true,
"user_role": "ADMIN",
"receive_motion_report_email": false
},
{
"artkey": 102,
"permission_change_dimming_scheme": false,
"permission_change_light_level": false,
"receive_email_report": true,
"receive_report_daily": true,
"receive_report_weekly": false,
"receive_report_monthly": false,
"allow_camera_portal": false,
"user_role": "USER",
"receive_motion_report_email": true
},
{
"artkey": 103,
"permission_change_dimming_scheme": true,
"permission_change_light_level": true,
"receive_email_report": false,
"receive_report_daily": false,
"receive_report_weekly": false,
"receive_report_monthly": false,
"allow_camera_portal": true,
"user_role": "ADMIN",
"receive_motion_report_email": false
}
]'
Request body
[
{
"artkey": 101,
"permission_change_dimming_scheme": true,
"permission_change_light_level": true,
"receive_email_report": true,
"receive_report_daily": false,
"receive_report_weekly": true,
"receive_report_monthly": true,
"allow_camera_portal": true,
"user_role": "ADMIN",
"receive_motion_report_email": false
},
{
"artkey": 102,
"permission_change_dimming_scheme": false,
"permission_change_light_level": false,
"receive_email_report": true,
"receive_report_daily": true,
"receive_report_weekly": false,
"receive_report_monthly": false,
"allow_camera_portal": false,
"user_role": "USER",
"receive_motion_report_email": true
},
{
"artkey": 103,
"permission_change_dimming_scheme": true,
"permission_change_light_level": true,
"receive_email_report": false,
"receive_report_daily": false,
"receive_report_weekly": false,
"receive_report_monthly": false,
"allow_camera_portal": true,
"user_role": "ADMIN",
"receive_motion_report_email": false
}
]
Response (200 OK)
{
"message": "3 users updated successfully.",
"updated": 3,
"errors": []
}
Response with partial failure (200 OK)
{
"message": "2 of 3 users updated successfully.",
"updated": 2,
"errors": [
{
"artkey": 103,
"error": "User not found."
}
]
}
Bulk update multiple users in a single request. The request body is an array of user objects. Each object must include the artkey field to identify the user, along with any fields to update. This endpoint is used for managing user permissions, report preferences, and roles across multiple users at once.
Request Body (Array of Objects)
| Field | Type | Required | Description |
|---|---|---|---|
| artkey | integer | Yes | The unique user identifier. |
| permission_change_dimming_scheme | boolean | No | Whether the user can modify dimming schemes. |
| permission_change_light_level | boolean | No | Whether the user can modify light levels. |
| receive_email_report | boolean | No | Whether the user receives email reports. |
| receive_report_daily | boolean | No | Whether the user receives daily reports. |
| receive_report_weekly | boolean | No | Whether the user receives weekly reports. |
| receive_report_monthly | boolean | No | Whether the user receives monthly reports. |
| allow_camera_portal | boolean | No | Whether the user has access to the camera portal. |
| user_role | string | No | The user's role. Accepted values: ADMIN, USER. |
| receive_motion_report_email | boolean | No | Whether the user receives motion detection report emails. |
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid request body or malformed user data. |
| 401 | Not authenticated or admin token expired. |
| 403 | Insufficient admin privileges. |
| 422 | Invalid user_role value. Must be ADMIN or USER. |
TALQ API
The TALQ API provides integration with the TALQ Smart City Protocol, an international standard for managing outdoor lighting and other smart city devices. TALQ defines a standardised interface between Central Management Software (CMS) and Outdoor Device Networks (ODN), enabling interoperable control of intelligent outdoor lighting infrastructure.
The Sustainder Brokerage Layer implements the TALQ protocol to communicate with TALQ-compliant gateways. Through these endpoints, you can manage devices, calendars, control programs, and issue commands to gateways that speak the TALQ protocol.
The base URL for the TALQ API is https://httpapi.sustainder.com/talq.
This API communicates in JSON.
Key Concepts
| Concept | Description |
|---|---|
| Gateway | A TALQ-compliant gateway that manages a network of outdoor devices (e.g., luminaires). |
| Device | A physical device (e.g., a streetlight controller) registered through a TALQ gateway. |
| Device Class | A template that defines the functions and attributes available on a category of devices. |
| Calendar | A schedule that assigns control programs to specific time periods for device operation. |
| Control Program | A set of time-based instructions (e.g., dimming levels) that define how devices behave during a period. |
| Service | A TALQ service capability advertised by a gateway. |
| Logger Config | A configuration that defines which device attributes to log and how to report them. |
Authentication
All TALQ API endpoints require JWT authentication. Include the token in the Authorization header:
Authorization: JWT {your_token}
See the Authentication section for details on obtaining a JWT token.
TALQ Services
TALQ services represent the capabilities advertised by TALQ gateways. Each service describes a functional capability that the gateway and its connected devices can provide (e.g., lighting control, metering, sensor data).
/talq/services [GET]
Request
curl "https://httpapi.sustainder.com/talq/services" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"services": [
{
"serviceName": "LightingService",
"version": "2.4.0",
"description": "TALQ Lighting control service for outdoor luminaires",
"serviceType": "lighting",
"gateway_address": "urn:talq:gateway:550e8400-e29b-41d4-a716-446655440000",
"supported_functions": [
"dimmingLevel",
"onOff",
"colorTemperature"
]
},
{
"serviceName": "MeteringService",
"version": "2.4.0",
"description": "Energy metering service for power consumption tracking",
"serviceType": "metering",
"gateway_address": "urn:talq:gateway:550e8400-e29b-41d4-a716-446655440000",
"supported_functions": [
"activePower",
"activeEnergy",
"voltage"
]
}
]
}
Lists all TALQ services that have been announced by connected gateways. Each service describes a capability and the functions it supports.
Response Fields
| Field | Type | Description |
|---|---|---|
| serviceName | string | Unique name identifier for the service. |
| version | string | TALQ protocol version supported by this service. |
| description | string | Human-readable description of the service. |
| serviceType | string | Category of the service (e.g., lighting, metering, sensor). |
| gateway_address | string | URN of the gateway that provides this service. |
| supported_functions | array | List of function names this service supports. |
/talq/services/{serviceName} [GET]
Request
curl "https://httpapi.sustainder.com/talq/services/LightingService" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"serviceName": "LightingService",
"version": "2.4.0",
"description": "TALQ Lighting control service for outdoor luminaires",
"serviceType": "lighting",
"gateway_address": "urn:talq:gateway:550e8400-e29b-41d4-a716-446655440000",
"supported_functions": [
"dimmingLevel",
"onOff",
"colorTemperature"
],
"configuration": {
"max_devices": 1000,
"supports_multicast": true,
"supports_logging": true
}
}
Returns detailed information about a specific TALQ service.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| serviceName | string | The unique name of the TALQ service (e.g., LightingService). |
Response Fields
Same fields as the service list response, plus:
| Field | Type | Description |
|---|---|---|
| configuration | object | Service-specific configuration details. |
| configuration.max_devices | integer | Maximum number of devices this service can manage. |
| configuration.supports_multicast | boolean | Whether the service supports multicast commands. |
| configuration.supports_logging | boolean | Whether the service supports data logging. |
Error Responses
| Status | Description |
|---|---|
| 404 | Service not found. |
TALQ Devices
TALQ devices represent physical equipment (e.g., luminaire controllers, sensors, meters) that are managed through TALQ gateways. Each device has a unique address, belongs to a device class, and exposes one or more functions with configurable attributes.
/talq/devices [GET]
Request
curl "https://httpapi.sustainder.com/talq/devices?offset=0&limit=50" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"devices": [
{
"address": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"deviceClass": "LuminaireController",
"gateway_address": "urn:talq:gateway:550e8400-e29b-41d4-a716-446655440000",
"status": "operational",
"description": "Streetlight controller - Keizersgracht 42",
"functions": [
{
"functionId": "dimmingLevel",
"currentValue": 75,
"unit": "percent"
},
{
"functionId": "onOff",
"currentValue": true,
"unit": "boolean"
}
],
"last_communication": "2024-11-15T14:32:08Z"
},
{
"address": "urn:talq:device:b2c3d4e5-f6a7-8901-bcde-f12345678901",
"deviceClass": "LuminaireController",
"gateway_address": "urn:talq:gateway:550e8400-e29b-41d4-a716-446655440000",
"status": "operational",
"description": "Streetlight controller - Herengracht 108",
"functions": [
{
"functionId": "dimmingLevel",
"currentValue": 100,
"unit": "percent"
},
{
"functionId": "onOff",
"currentValue": true,
"unit": "boolean"
}
],
"last_communication": "2024-11-15T14:30:45Z"
}
],
"total": 256,
"offset": 0,
"limit": 50
}
Lists all TALQ devices registered through connected gateways. Supports pagination via offset and limit query parameters.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| offset | integer | 0 | Number of records to skip before returning results. |
| limit | integer | 50 | Maximum number of devices to return per request. |
Response Fields
| Field | Type | Description |
|---|---|---|
| address | string | Unique TALQ URN address of the device. |
| deviceClass | string | Name of the device class this device belongs to. |
| gateway_address | string | URN of the gateway managing this device. |
| status | string | Device status: operational, degraded, not_operational, or unknown. |
| description | string | Human-readable description of the device. |
| functions | array | List of functions exposed by this device. |
| functions[].functionId | string | Unique identifier for the function. |
| functions[].currentValue | mixed | Current value of the function. |
| functions[].unit | string | Unit of measurement for the value. |
| last_communication | string | ISO 8601 timestamp of the last communication with the device. |
| total | integer | Total number of devices available. |
| offset | integer | Current offset in the result set. |
| limit | integer | Current page size limit. |
/talq/devices/count [GET]
Request
curl "https://httpapi.sustainder.com/talq/devices/count" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"count": 256
}
Returns the total number of TALQ devices registered in the system.
Response Fields
| Field | Type | Description |
|---|---|---|
| count | integer | Total number of TALQ devices. |
/talq/devices/{deviceAddress} [GET]
Request
curl "https://httpapi.sustainder.com/talq/devices/urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"address": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"deviceClass": "LuminaireController",
"gateway_address": "urn:talq:gateway:550e8400-e29b-41d4-a716-446655440000",
"status": "operational",
"description": "Streetlight controller - Keizersgracht 42",
"functions": [
{
"functionId": "dimmingLevel",
"currentValue": 75,
"unit": "percent",
"writable": true,
"minValue": 0,
"maxValue": 100
},
{
"functionId": "onOff",
"currentValue": true,
"unit": "boolean",
"writable": true
},
{
"functionId": "activePower",
"currentValue": 42.5,
"unit": "watt",
"writable": false
},
{
"functionId": "operatingHours",
"currentValue": 18240,
"unit": "hours",
"writable": false
}
],
"calendar_address": "urn:talq:calendar:c3d4e5f6-a7b8-9012-cdef-123456789012",
"last_communication": "2024-11-15T14:32:08Z",
"firmware_version": "3.2.1"
}
Returns detailed information for a specific TALQ device, including all functions and their current values.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| deviceAddress | string | The TALQ URN address of the device. |
Response Fields
Same fields as the device list response, plus:
| Field | Type | Description |
|---|---|---|
| functions[].writable | boolean | Whether this function can be written to (controlled). |
| functions[].minValue | number | Minimum allowed value (for numeric writable functions). |
| functions[].maxValue | number | Maximum allowed value (for numeric writable functions). |
| calendar_address | string | URN of the calendar currently assigned to this device. |
| firmware_version | string | Current firmware version of the device. |
Error Responses
| Status | Description |
|---|---|
| 404 | Device not found. |
/talq/devices/{deviceAddress}/{functionId} [GET]
Request
curl "https://httpapi.sustainder.com/talq/devices/urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/dimmingLevel" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"functionId": "dimmingLevel",
"currentValue": 75,
"unit": "percent",
"writable": true,
"minValue": 0,
"maxValue": 100,
"attributes": [
{
"attributeName": "targetLevel",
"value": 75,
"type": "integer",
"writable": true
},
{
"attributeName": "fadeTime",
"value": 2000,
"type": "integer",
"writable": true,
"unit": "milliseconds"
},
{
"attributeName": "actualLevel",
"value": 74,
"type": "integer",
"writable": false
}
],
"last_updated": "2024-11-15T14:32:08Z"
}
Returns the details of a specific function on a TALQ device, including all attributes of that function.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| deviceAddress | string | The TALQ URN address of the device. |
| functionId | string | The function identifier (e.g., dimmingLevel, onOff, activePower). |
Response Fields
| Field | Type | Description |
|---|---|---|
| functionId | string | The function identifier. |
| currentValue | mixed | Current value of the function. |
| unit | string | Unit of measurement. |
| writable | boolean | Whether this function can be written to. |
| minValue | number | Minimum allowed value. |
| maxValue | number | Maximum allowed value. |
| attributes | array | List of individual attributes within this function. |
| attributes[].attributeName | string | Name of the attribute. |
| attributes[].value | mixed | Current value of the attribute. |
| attributes[].type | string | Data type: integer, float, boolean, string. |
| attributes[].writable | boolean | Whether this attribute is writable. |
| attributes[].unit | string | Unit of measurement (if applicable). |
| last_updated | string | ISO 8601 timestamp of the last value update. |
Error Responses
| Status | Description |
|---|---|
| 404 | Device or function not found. |
/talq/devices/{deviceAddress}/{functionId}/{attributeName} [GET]
Request
curl "https://httpapi.sustainder.com/talq/devices/urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/dimmingLevel/targetLevel" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"attributeName": "targetLevel",
"value": 75,
"type": "integer",
"writable": true,
"unit": "percent",
"minValue": 0,
"maxValue": 100,
"last_updated": "2024-11-15T14:32:08Z"
}
Returns the value and metadata of a specific attribute within a device function.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| deviceAddress | string | The TALQ URN address of the device. |
| functionId | string | The function identifier (e.g., dimmingLevel). |
| attributeName | string | The attribute name (e.g., targetLevel, fadeTime). |
Response Fields
| Field | Type | Description |
|---|---|---|
| attributeName | string | Name of the attribute. |
| value | mixed | Current value of the attribute. |
| type | string | Data type: integer, float, boolean, string. |
| writable | boolean | Whether this attribute can be modified. |
| unit | string | Unit of measurement (if applicable). |
| minValue | number | Minimum allowed value (for numeric attributes). |
| maxValue | number | Maximum allowed value (for numeric attributes). |
| last_updated | string | ISO 8601 timestamp of the last value update. |
Error Responses
| Status | Description |
|---|---|
| 404 | Device, function, or attribute not found. |
TALQ Device Classes
Device classes define templates for categories of TALQ devices. A device class specifies which functions and attributes are available on devices of that type. For example, a LuminaireController device class defines functions like dimmingLevel and onOff, while a PowerMeter class defines functions like activePower and activeEnergy.
/talq/device-classes [GET]
Request
curl "https://httpapi.sustainder.com/talq/device-classes" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"device_classes": [
{
"className": "LuminaireController",
"description": "TALQ luminaire controller with dimming and on/off capability",
"version": "2.4.0",
"functions": [
{
"functionId": "dimmingLevel",
"description": "Controls the dimming level of the luminaire",
"type": "numeric",
"writable": true
},
{
"functionId": "onOff",
"description": "On/Off control for the luminaire",
"type": "boolean",
"writable": true
},
{
"functionId": "activePower",
"description": "Current active power consumption",
"type": "numeric",
"writable": false
}
]
},
{
"className": "PowerMeter",
"description": "Energy metering device for power consumption monitoring",
"version": "2.4.0",
"functions": [
{
"functionId": "activePower",
"description": "Current active power consumption in watts",
"type": "numeric",
"writable": false
},
{
"functionId": "activeEnergy",
"description": "Cumulative active energy in kilowatt-hours",
"type": "numeric",
"writable": false
},
{
"functionId": "voltage",
"description": "Current supply voltage",
"type": "numeric",
"writable": false
}
]
}
],
"total": 2
}
Lists all TALQ device classes that have been registered by connected gateways.
Response Fields
| Field | Type | Description |
|---|---|---|
| className | string | Unique name of the device class. |
| description | string | Human-readable description of the device class. |
| version | string | TALQ protocol version of this device class definition. |
| functions | array | List of functions available on devices of this class. |
| functions[].functionId | string | Unique identifier for the function. |
| functions[].description | string | Description of the function. |
| functions[].type | string | Data type: numeric, boolean, string, enum. |
| functions[].writable | boolean | Whether the function can be controlled (written to). |
| total | integer | Total number of device classes. |
/talq/device-classes/count [GET]
Request
curl "https://httpapi.sustainder.com/talq/device-classes/count" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"count": 2
}
Returns the total number of TALQ device classes registered in the system.
Response Fields
| Field | Type | Description |
|---|---|---|
| count | integer | Total number of device classes. |
/talq/device-classes/{className} [GET]
Request
curl "https://httpapi.sustainder.com/talq/device-classes/LuminaireController" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"className": "LuminaireController",
"description": "TALQ luminaire controller with dimming and on/off capability",
"version": "2.4.0",
"functions": [
{
"functionId": "dimmingLevel",
"description": "Controls the dimming level of the luminaire",
"type": "numeric",
"writable": true,
"attributes": [
{
"attributeName": "targetLevel",
"type": "integer",
"unit": "percent",
"minValue": 0,
"maxValue": 100,
"writable": true
},
{
"attributeName": "fadeTime",
"type": "integer",
"unit": "milliseconds",
"minValue": 0,
"maxValue": 60000,
"writable": true
},
{
"attributeName": "actualLevel",
"type": "integer",
"unit": "percent",
"writable": false
}
]
},
{
"functionId": "onOff",
"description": "On/Off control for the luminaire",
"type": "boolean",
"writable": true,
"attributes": [
{
"attributeName": "state",
"type": "boolean",
"writable": true
}
]
},
{
"functionId": "activePower",
"description": "Current active power consumption",
"type": "numeric",
"writable": false,
"attributes": [
{
"attributeName": "value",
"type": "float",
"unit": "watt",
"writable": false
}
]
}
]
}
Returns full details for a specific device class, including all functions and their attribute definitions.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| className | string | The unique name of the device class (e.g., LuminaireController). |
Response Fields
Same fields as the device class list response, with additional attribute details per function:
| Field | Type | Description |
|---|---|---|
| functions[].attributes | array | Detailed attribute definitions for this function. |
| functions[].attributes[].attributeName | string | Name of the attribute. |
| functions[].attributes[].type | string | Data type: integer, float, boolean, string. |
| functions[].attributes[].unit | string | Unit of measurement (if applicable). |
| functions[].attributes[].minValue | number | Minimum allowed value (for numeric attributes). |
| functions[].attributes[].maxValue | number | Maximum allowed value (for numeric attributes). |
| functions[].attributes[].writable | boolean | Whether this attribute can be modified. |
Error Responses
| Status | Description |
|---|---|
| 404 | Device class not found. |
TALQ Calendars
Calendars in TALQ define schedules that assign control programs to specific time periods. A calendar is the primary mechanism for automating device behaviour -- it tells devices which control program to follow at any given time. Each calendar has a default program and a set of rules that override the default for specific date ranges.
Calendars are assigned to devices using the Assign Commands endpoint.
/talq/calendars [GET]
Request
curl "https://httpapi.sustainder.com/talq/calendars?offset=0&limit=50" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"calendars": [
{
"address": "urn:talq:calendar:c3d4e5f6-a7b8-9012-cdef-123456789012",
"owner_cms": true,
"default_program": "urn:talq:program:d4e5f6a7-b890-1234-cdef-234567890123",
"rules": [
{
"start_date": "2024-12-01",
"end_date": "2025-02-28",
"control_program": "urn:talq:program:e5f6a7b8-9012-3456-cdef-345678901234",
"description": "Winter extended lighting"
}
],
"description": "Standard residential street calendar"
},
{
"address": "urn:talq:calendar:f6a7b8c9-0123-4567-cdef-456789012345",
"owner_cms": true,
"default_program": "urn:talq:program:a7b8c9d0-1234-5678-cdef-567890123456",
"rules": [],
"description": "Highway calendar - constant brightness"
}
],
"total": 2,
"offset": 0,
"limit": 50
}
Lists all TALQ calendars configured in the system. Supports pagination via offset and limit query parameters.
Query Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| offset | integer | 0 | Number of records to skip before returning results. |
| limit | integer | 50 | Maximum number of calendars to return per request. |
Response Fields
| Field | Type | Description |
|---|---|---|
| address | string | Unique TALQ URN address of the calendar. |
| owner_cms | boolean | Whether the CMS (Sustainder) owns this calendar. |
| default_program | string | URN address of the default control program. |
| rules | array | List of rules that override the default program for specific date ranges. |
| rules[].start_date | string | Start date of the rule (YYYY-MM-DD). |
| rules[].end_date | string | End date of the rule (YYYY-MM-DD). |
| rules[].control_program | string | URN address of the control program for this rule. |
| rules[].description | string | Human-readable description of the rule. |
| description | string | Human-readable description of the calendar. |
| total | integer | Total number of calendars available. |
/talq/calendars/count [GET]
Request
curl "https://httpapi.sustainder.com/talq/calendars/count" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"count": 2
}
Returns the total number of TALQ calendars configured in the system.
Response Fields
| Field | Type | Description |
|---|---|---|
| count | integer | Total number of calendars. |
/talq/calendars/{calendarAddress} [GET]
Request
curl "https://httpapi.sustainder.com/talq/calendars/urn:talq:calendar:c3d4e5f6-a7b8-9012-cdef-123456789012" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"address": "urn:talq:calendar:c3d4e5f6-a7b8-9012-cdef-123456789012",
"owner_cms": true,
"default_program": "urn:talq:program:d4e5f6a7-b890-1234-cdef-234567890123",
"rules": [
{
"start_date": "2024-12-01",
"end_date": "2025-02-28",
"control_program": "urn:talq:program:e5f6a7b8-9012-3456-cdef-345678901234",
"description": "Winter extended lighting"
}
],
"description": "Standard residential street calendar",
"assigned_devices": [
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"urn:talq:device:b2c3d4e5-f6a7-8901-bcde-f12345678901"
]
}
Returns detailed information for a specific calendar, including the list of devices it is assigned to.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| calendarAddress | string | The TALQ URN address of the calendar. |
Response Fields
Same fields as the calendar list response, plus:
| Field | Type | Description |
|---|---|---|
| assigned_devices | array | List of device URN addresses this calendar is currently assigned to. |
Error Responses
| Status | Description |
|---|---|
| 404 | Calendar not found. |
/talq/calendars [POST]
Request
curl -X POST "https://httpapi.sustainder.com/talq/calendars" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"id": "urn:talq:calendar:new-calendar-001",
"owner_cms": true,
"default_program": "urn:talq:program:d4e5f6a7-b890-1234-cdef-234567890123",
"rules": [
{
"start_date": "2025-06-01",
"end_date": "2025-08-31",
"control_program": "urn:talq:program:a7b8c9d0-1234-5678-cdef-567890123456",
"description": "Summer energy saving mode"
}
],
"description": "Park pathway calendar"
}'
Request body
{
"id": "urn:talq:calendar:new-calendar-001",
"owner_cms": true,
"default_program": "urn:talq:program:d4e5f6a7-b890-1234-cdef-234567890123",
"rules": [
{
"start_date": "2025-06-01",
"end_date": "2025-08-31",
"control_program": "urn:talq:program:a7b8c9d0-1234-5678-cdef-567890123456",
"description": "Summer energy saving mode"
}
],
"description": "Park pathway calendar"
}
Response (201 Created)
{
"address": "urn:talq:calendar:new-calendar-001",
"owner_cms": true,
"default_program": "urn:talq:program:d4e5f6a7-b890-1234-cdef-234567890123",
"rules": [
{
"start_date": "2025-06-01",
"end_date": "2025-08-31",
"control_program": "urn:talq:program:a7b8c9d0-1234-5678-cdef-567890123456",
"description": "Summer energy saving mode"
}
],
"description": "Park pathway calendar"
}
Creates a new TALQ calendar. The calendar defines a default control program and optional rules that override the default for specific date ranges.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| id | string | Yes | Unique URN identifier for the new calendar. |
| owner_cms | boolean | No | Whether the CMS owns this calendar. Defaults to true. |
| default_program | string | Yes | URN address of the default control program to use. |
| rules | array | No | List of rules that override the default program. Defaults to empty array. |
| rules[].start_date | string | Yes | Start date of the rule (YYYY-MM-DD). |
| rules[].end_date | string | Yes | End date of the rule (YYYY-MM-DD). |
| rules[].control_program | string | Yes | URN address of the control program for this rule. |
| rules[].description | string | No | Human-readable description of the rule. |
| description | string | No | Human-readable description of the calendar. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., missing required fields, invalid control program reference). |
| 409 | Calendar with the specified ID already exists. |
/talq/calendars/{calendarAddress} [PATCH]
Request
curl -X PATCH "https://httpapi.sustainder.com/talq/calendars/urn:talq:calendar:c3d4e5f6-a7b8-9012-cdef-123456789012" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"default_program": "urn:talq:program:a7b8c9d0-1234-5678-cdef-567890123456",
"rules": [
{
"start_date": "2025-01-01",
"end_date": "2025-03-31",
"control_program": "urn:talq:program:e5f6a7b8-9012-3456-cdef-345678901234",
"description": "Q1 winter schedule"
},
{
"start_date": "2025-06-01",
"end_date": "2025-08-31",
"control_program": "urn:talq:program:d4e5f6a7-b890-1234-cdef-234567890123",
"description": "Summer schedule"
}
]
}'
Request body
{
"default_program": "urn:talq:program:a7b8c9d0-1234-5678-cdef-567890123456",
"rules": [
{
"start_date": "2025-01-01",
"end_date": "2025-03-31",
"control_program": "urn:talq:program:e5f6a7b8-9012-3456-cdef-345678901234",
"description": "Q1 winter schedule"
},
{
"start_date": "2025-06-01",
"end_date": "2025-08-31",
"control_program": "urn:talq:program:d4e5f6a7-b890-1234-cdef-234567890123",
"description": "Summer schedule"
}
]
}
Response (200 OK)
{
"address": "urn:talq:calendar:c3d4e5f6-a7b8-9012-cdef-123456789012",
"owner_cms": true,
"default_program": "urn:talq:program:a7b8c9d0-1234-5678-cdef-567890123456",
"rules": [
{
"start_date": "2025-01-01",
"end_date": "2025-03-31",
"control_program": "urn:talq:program:e5f6a7b8-9012-3456-cdef-345678901234",
"description": "Q1 winter schedule"
},
{
"start_date": "2025-06-01",
"end_date": "2025-08-31",
"control_program": "urn:talq:program:d4e5f6a7-b890-1234-cdef-234567890123",
"description": "Summer schedule"
}
],
"description": "Standard residential street calendar"
}
Updates an existing TALQ calendar. Only the fields provided in the request body are updated; omitted fields remain unchanged.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| calendarAddress | string | The TALQ URN address of the calendar to update. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| default_program | string | No | New default control program URN. |
| rules | array | No | Replacement list of rules. This replaces all existing rules. |
| description | string | No | Updated description. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., invalid control program reference). |
| 404 | Calendar not found. |
/talq/calendars/{calendarAddress} [DELETE]
Request
curl -X DELETE "https://httpapi.sustainder.com/talq/calendars/urn:talq:calendar:c3d4e5f6-a7b8-9012-cdef-123456789012" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (204 No Content)
204 No Content
Deletes a TALQ calendar. The calendar must not be currently assigned to any devices.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| calendarAddress | string | The TALQ URN address of the calendar to delete. |
Error Responses
| Status | Description |
|---|---|
| 404 | Calendar not found. |
| 409 | Calendar is still assigned to one or more devices. Unassign the calendar first. |
TALQ Control Programs
Control programs define the operational behaviour for TALQ devices during a specific time period. A control program consists of active periods (when the program is in effect) and fixed time controls (specific dimming levels at specific times). Control programs are assigned to devices through calendars.
/talq/control-programs [GET]
Request
curl "https://httpapi.sustainder.com/talq/control-programs" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"control_programs": [
{
"address": "urn:talq:program:d4e5f6a7-b890-1234-cdef-234567890123",
"description": "Standard evening dimming profile",
"activePeriods": [
{
"start": "sunset",
"startOffset": -15,
"end": "sunrise",
"endOffset": 10
}
],
"fixedTimeControls": [
{
"time": "sunset",
"offset": -15,
"dimmingLevel": 100
},
{
"time": "22:00",
"offset": 0,
"dimmingLevel": 75
},
{
"time": "00:00",
"offset": 0,
"dimmingLevel": 50
},
{
"time": "05:30",
"offset": 0,
"dimmingLevel": 100
},
{
"time": "sunrise",
"offset": 10,
"dimmingLevel": 0
}
]
},
{
"address": "urn:talq:program:e5f6a7b8-9012-3456-cdef-345678901234",
"description": "Winter extended lighting - high brightness",
"activePeriods": [
{
"start": "sunset",
"startOffset": -30,
"end": "sunrise",
"endOffset": 20
}
],
"fixedTimeControls": [
{
"time": "sunset",
"offset": -30,
"dimmingLevel": 100
},
{
"time": "23:00",
"offset": 0,
"dimmingLevel": 80
},
{
"time": "05:00",
"offset": 0,
"dimmingLevel": 100
},
{
"time": "sunrise",
"offset": 20,
"dimmingLevel": 0
}
]
}
],
"total": 2
}
Lists all TALQ control programs configured in the system.
Response Fields
| Field | Type | Description |
|---|---|---|
| address | string | Unique TALQ URN address of the control program. |
| description | string | Human-readable description of the program. |
| activePeriods | array | Time periods when this control program is active. |
| activePeriods[].start | string | Start trigger: sunset, sunrise, or a fixed time (HH:MM). |
| activePeriods[].startOffset | integer | Offset in minutes from the start trigger (negative = before). |
| activePeriods[].end | string | End trigger: sunset, sunrise, or a fixed time (HH:MM). |
| activePeriods[].endOffset | integer | Offset in minutes from the end trigger. |
| fixedTimeControls | array | Ordered list of dimming level changes during the active period. |
| fixedTimeControls[].time | string | Time trigger: sunset, sunrise, or a fixed time (HH:MM). |
| fixedTimeControls[].offset | integer | Offset in minutes from the time trigger. |
| fixedTimeControls[].dimmingLevel | integer | Target dimming level (0-100 percent). |
| total | integer | Total number of control programs. |
/talq/control-programs/count [GET]
Request
curl "https://httpapi.sustainder.com/talq/control-programs/count" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"count": 2
}
Returns the total number of TALQ control programs configured in the system.
Response Fields
| Field | Type | Description |
|---|---|---|
| count | integer | Total number of control programs. |
/talq/control-programs/{controlProgramAddress} [GET]
Request
curl "https://httpapi.sustainder.com/talq/control-programs/urn:talq:program:d4e5f6a7-b890-1234-cdef-234567890123" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"address": "urn:talq:program:d4e5f6a7-b890-1234-cdef-234567890123",
"description": "Standard evening dimming profile",
"activePeriods": [
{
"start": "sunset",
"startOffset": -15,
"end": "sunrise",
"endOffset": 10
}
],
"fixedTimeControls": [
{
"time": "sunset",
"offset": -15,
"dimmingLevel": 100
},
{
"time": "22:00",
"offset": 0,
"dimmingLevel": 75
},
{
"time": "00:00",
"offset": 0,
"dimmingLevel": 50
},
{
"time": "05:30",
"offset": 0,
"dimmingLevel": 100
},
{
"time": "sunrise",
"offset": 10,
"dimmingLevel": 0
}
],
"referenced_by_calendars": [
"urn:talq:calendar:c3d4e5f6-a7b8-9012-cdef-123456789012"
]
}
Returns detailed information for a specific control program, including which calendars reference it.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| controlProgramAddress | string | The TALQ URN address of the control program. |
Response Fields
Same fields as the control program list response, plus:
| Field | Type | Description |
|---|---|---|
| referenced_by_calendars | array | List of calendar URN addresses that reference this control program. |
Error Responses
| Status | Description |
|---|---|
| 404 | Control program not found. |
/talq/control-programs [POST]
Request
curl -X POST "https://httpapi.sustainder.com/talq/control-programs" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"description": "Festival mode - full brightness all night",
"activePeriods": [
{
"start": "sunset",
"startOffset": -30,
"end": "sunrise",
"endOffset": 15
}
],
"fixedTimeControls": [
{
"time": "sunset",
"offset": -30,
"dimmingLevel": 100
},
{
"time": "sunrise",
"offset": 15,
"dimmingLevel": 0
}
]
}'
Request body
{
"description": "Festival mode - full brightness all night",
"activePeriods": [
{
"start": "sunset",
"startOffset": -30,
"end": "sunrise",
"endOffset": 15
}
],
"fixedTimeControls": [
{
"time": "sunset",
"offset": -30,
"dimmingLevel": 100
},
{
"time": "sunrise",
"offset": 15,
"dimmingLevel": 0
}
]
}
Response (201 Created)
{
"address": "urn:talq:program:f6a7b8c9-0123-4567-cdef-456789012345",
"description": "Festival mode - full brightness all night",
"activePeriods": [
{
"start": "sunset",
"startOffset": -30,
"end": "sunrise",
"endOffset": 15
}
],
"fixedTimeControls": [
{
"time": "sunset",
"offset": -30,
"dimmingLevel": 100
},
{
"time": "sunrise",
"offset": 15,
"dimmingLevel": 0
}
]
}
Creates a new TALQ control program. The program can then be referenced by calendars to define device behaviour.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| description | string | No | Human-readable description of the control program. |
| activePeriods | array | Yes | At least one active period defining when the program is in effect. |
| activePeriods[].start | string | Yes | Start trigger: sunset, sunrise, or fixed time (HH:MM). |
| activePeriods[].startOffset | integer | No | Offset in minutes from start trigger. Defaults to 0. |
| activePeriods[].end | string | Yes | End trigger: sunset, sunrise, or fixed time (HH:MM). |
| activePeriods[].endOffset | integer | No | Offset in minutes from end trigger. Defaults to 0. |
| fixedTimeControls | array | Yes | At least one time-based dimming control step. |
| fixedTimeControls[].time | string | Yes | Time trigger: sunset, sunrise, or fixed time (HH:MM). |
| fixedTimeControls[].offset | integer | No | Offset in minutes from the time trigger. Defaults to 0. |
| fixedTimeControls[].dimmingLevel | integer | Yes | Target dimming level (0-100). |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., missing required fields, dimming level out of range, no active periods). |
TALQ Commands
TALQ commands allow you to send operational instructions to devices through their gateways. There are two types of commands:
- Assign commands assign calendars or other resources to devices, defining their long-term scheduling behaviour.
- Override commands send temporary overrides to devices, taking immediate effect and expiring after a specified duration.
/talq/assign-commands [POST]
Request - Assign a calendar to devices
curl -X POST "https://httpapi.sustainder.com/talq/assign-commands" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"entity": "calendars",
"addresses": [
{
"device_address": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"resource_address": "urn:talq:calendar:c3d4e5f6-a7b8-9012-cdef-123456789012"
},
{
"device_address": "urn:talq:device:b2c3d4e5-f6a7-8901-bcde-f12345678901",
"resource_address": "urn:talq:calendar:c3d4e5f6-a7b8-9012-cdef-123456789012"
}
]
}'
Request body
{
"entity": "calendars",
"addresses": [
{
"device_address": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"resource_address": "urn:talq:calendar:c3d4e5f6-a7b8-9012-cdef-123456789012"
},
{
"device_address": "urn:talq:device:b2c3d4e5-f6a7-8901-bcde-f12345678901",
"resource_address": "urn:talq:calendar:c3d4e5f6-a7b8-9012-cdef-123456789012"
}
]
}
Response (200 OK)
{
"status": "accepted",
"assigned": 2,
"results": [
{
"device_address": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "success"
},
{
"device_address": "urn:talq:device:b2c3d4e5-f6a7-8901-bcde-f12345678901",
"status": "success"
}
]
}
Assigns calendars or other resources to one or more TALQ devices. This is the primary mechanism for setting up long-term scheduling behaviour on devices.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| entity | string | No | Type of resource to assign. Defaults to calendars. |
| addresses | array | Yes | List of device-to-resource assignments. Must contain at least one entry. |
| addresses[].device_address | string | Yes | TALQ URN address of the target device. |
| addresses[].resource_address | string | Yes | TALQ URN address of the resource (e.g., calendar) to assign. |
Response Fields
| Field | Type | Description |
|---|---|---|
| status | string | Overall command status: accepted or rejected. |
| assigned | integer | Number of devices successfully assigned. |
| results | array | Per-device assignment results. |
| results[].device_address | string | The device that was targeted. |
| results[].status | string | Result for this device: success or failed. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., empty addresses array, invalid resource references). |
| 404 | One or more device or resource addresses not found. |
/talq/override-commands [POST]
Request - Override devices to 50% dimming
curl -X POST "https://httpapi.sustainder.com/talq/override-commands" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"addresses": [
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"urn:talq:device:b2c3d4e5-f6a7-8901-bcde-f12345678901"
],
"target_command": {
"dimmingLevel": 50,
"expiration_minutes": 120
}
}'
Request body
{
"addresses": [
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"urn:talq:device:b2c3d4e5-f6a7-8901-bcde-f12345678901"
],
"target_command": {
"dimmingLevel": 50,
"expiration_minutes": 120
}
}
Response (200 OK)
{
"status": "accepted",
"overridden": 2,
"expiration": "2024-11-15T16:32:08Z",
"results": [
{
"device_address": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "success"
},
{
"device_address": "urn:talq:device:b2c3d4e5-f6a7-8901-bcde-f12345678901",
"status": "success"
}
]
}
Request - Clear overrides on devices
curl -X POST "https://httpapi.sustainder.com/talq/override-commands" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"addresses": [
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"urn:talq:device:b2c3d4e5-f6a7-8901-bcde-f12345678901"
],
"target_command": null
}'
Request body (clear overrides)
{
"addresses": [
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"urn:talq:device:b2c3d4e5-f6a7-8901-bcde-f12345678901"
],
"target_command": null
}
Response (200 OK)
{
"status": "accepted",
"overridden": 2,
"expiration": null,
"results": [
{
"device_address": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "success"
},
{
"device_address": "urn:talq:device:b2c3d4e5-f6a7-8901-bcde-f12345678901",
"status": "success"
}
]
}
Sends temporary override commands to one or more TALQ devices. Overrides take immediate effect and expire after the specified duration, at which point devices return to their calendar-defined behaviour.
Set target_command to null to clear any active overrides on the specified devices.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| addresses | array | Yes | List of device URN addresses to send the override to. |
| target_command | object\ | null | Yes |
| target_command.dimmingLevel | integer | Yes | Target dimming level (0-100). |
| target_command.expiration_minutes | integer | Yes | Duration in minutes before the override expires. Maximum: 240 minutes. |
Response Fields
| Field | Type | Description |
|---|---|---|
| status | string | Overall command status: accepted or rejected. |
| overridden | integer | Number of devices successfully overridden. |
| expiration | string\ | null |
| results | array | Per-device override results. |
| results[].device_address | string | The device that was targeted. |
| results[].status | string | Result for this device: success or failed. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., expiration exceeds 240 minutes, empty addresses, dimming level out of range). |
| 404 | One or more device addresses not found. |
TALQ Logging
TALQ logging allows you to configure data collection from device attributes. Logger configurations define which attributes to record, how to record them (recording mode), and how to report the collected data (reporting mode). Log reports contain the actual recorded data from the configured loggers.
/talq/logger-configs [GET]
Request
curl "https://httpapi.sustainder.com/talq/logger-configs" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"logger_configs": [
{
"address": "urn:talq:logger:l1a2b3c4-d5e6-7890-abcd-ef1234567890",
"recording_mode": "periodic",
"reporting_mode": "on_demand",
"source_addresses": [
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activePower/value",
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activeEnergy/value"
],
"description": "Power monitoring - Keizersgracht 42",
"interval_minutes": 15
},
{
"address": "urn:talq:logger:m2b3c4d5-e6f7-8901-bcde-f12345678901",
"recording_mode": "on_change",
"reporting_mode": "periodic",
"source_addresses": [
"urn:talq:device:b2c3d4e5-f6a7-8901-bcde-f12345678901/dimmingLevel/actualLevel"
],
"description": "Dimming level change log - Herengracht 108",
"reporting_interval_minutes": 60
}
],
"total": 2
}
Lists all TALQ logger configurations.
Response Fields
| Field | Type | Description |
|---|---|---|
| address | string | Unique TALQ URN address of the logger configuration. |
| recording_mode | string | How data is recorded: periodic (at fixed intervals) or on_change (when values change). |
| reporting_mode | string | How data is reported: periodic (at fixed intervals), on_demand (when requested), or on_threshold (when thresholds are exceeded). |
| source_addresses | array | List of device attribute URN paths to record (format: {deviceAddress}/{functionId}/{attributeName}). |
| description | string | Human-readable description of the logger configuration. |
| interval_minutes | integer | Recording interval in minutes (for periodic recording mode). |
| reporting_interval_minutes | integer | Reporting interval in minutes (for periodic reporting mode). |
| total | integer | Total number of logger configurations. |
/talq/logger-configs/{loggerAddress} [GET]
Request
curl "https://httpapi.sustainder.com/talq/logger-configs/urn:talq:logger:l1a2b3c4-d5e6-7890-abcd-ef1234567890" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"address": "urn:talq:logger:l1a2b3c4-d5e6-7890-abcd-ef1234567890",
"recording_mode": "periodic",
"reporting_mode": "on_demand",
"source_addresses": [
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activePower/value",
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activeEnergy/value"
],
"description": "Power monitoring - Keizersgracht 42",
"interval_minutes": 15,
"created_at": "2024-10-01T10:00:00Z",
"last_report_at": "2024-11-15T14:15:00Z"
}
Returns detailed information for a specific logger configuration.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| loggerAddress | string | The TALQ URN address of the logger configuration. |
Response Fields
Same fields as the logger config list response, plus:
| Field | Type | Description |
|---|---|---|
| created_at | string | ISO 8601 timestamp of when the logger was created. |
| last_report_at | string | ISO 8601 timestamp of the most recent log report. |
Error Responses
| Status | Description |
|---|---|
| 404 | Logger configuration not found. |
/talq/logger-configs [POST]
Request
curl -X POST "https://httpapi.sustainder.com/talq/logger-configs" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"address": "urn:talq:logger:new-logger-001",
"recording_mode": "periodic",
"reporting_mode": "on_demand",
"source_addresses": [
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activePower/value",
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/voltage/value"
],
"description": "Voltage and power monitoring",
"interval_minutes": 30
}'
Request body
{
"address": "urn:talq:logger:new-logger-001",
"recording_mode": "periodic",
"reporting_mode": "on_demand",
"source_addresses": [
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activePower/value",
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/voltage/value"
],
"description": "Voltage and power monitoring",
"interval_minutes": 30
}
Response (201 Created)
{
"address": "urn:talq:logger:new-logger-001",
"recording_mode": "periodic",
"reporting_mode": "on_demand",
"source_addresses": [
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activePower/value",
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/voltage/value"
],
"description": "Voltage and power monitoring",
"interval_minutes": 30,
"created_at": "2024-11-15T15:00:00Z"
}
Creates a new TALQ logger configuration.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| address | string | Yes | Unique URN identifier for the new logger configuration. |
| recording_mode | string | Yes | Recording mode: periodic or on_change. |
| reporting_mode | string | Yes | Reporting mode: periodic, on_demand, or on_threshold. |
| source_addresses | array | Yes | List of device attribute URN paths to record. |
| description | string | No | Human-readable description. |
| interval_minutes | integer | Conditional | Recording interval in minutes. Required when recording_mode is periodic. |
| reporting_interval_minutes | integer | Conditional | Reporting interval in minutes. Required when reporting_mode is periodic. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., missing required fields, invalid source addresses). |
| 409 | Logger configuration with the specified address already exists. |
/talq/logger-configs/{loggerAddress} [PUT]
Request
curl -X PUT "https://httpapi.sustainder.com/talq/logger-configs/urn:talq:logger:l1a2b3c4-d5e6-7890-abcd-ef1234567890" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"recording_mode": "periodic",
"reporting_mode": "periodic",
"source_addresses": [
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activePower/value",
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activeEnergy/value",
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/voltage/value"
],
"description": "Extended power monitoring - Keizersgracht 42",
"interval_minutes": 10,
"reporting_interval_minutes": 60
}'
Request body
{
"recording_mode": "periodic",
"reporting_mode": "periodic",
"source_addresses": [
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activePower/value",
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activeEnergy/value",
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/voltage/value"
],
"description": "Extended power monitoring - Keizersgracht 42",
"interval_minutes": 10,
"reporting_interval_minutes": 60
}
Response (200 OK)
{
"address": "urn:talq:logger:l1a2b3c4-d5e6-7890-abcd-ef1234567890",
"recording_mode": "periodic",
"reporting_mode": "periodic",
"source_addresses": [
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activePower/value",
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activeEnergy/value",
"urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/voltage/value"
],
"description": "Extended power monitoring - Keizersgracht 42",
"interval_minutes": 10,
"reporting_interval_minutes": 60
}
Replaces the entire configuration of an existing logger. All fields in the request body are required as this performs a full replacement.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| loggerAddress | string | The TALQ URN address of the logger configuration to update. |
Request Parameters
Same as POST /talq/logger-configs, except address is not included (it is taken from the URL path).
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error. |
| 404 | Logger configuration not found. |
/talq/logger-configs/{loggerAddress} [DELETE]
Request
curl -X DELETE "https://httpapi.sustainder.com/talq/logger-configs/urn:talq:logger:l1a2b3c4-d5e6-7890-abcd-ef1234567890" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (204 No Content)
204 No Content
Deletes a TALQ logger configuration. Any pending log reports for this logger will still be available until they are retrieved.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| loggerAddress | string | The TALQ URN address of the logger configuration to delete. |
Error Responses
| Status | Description |
|---|---|
| 404 | Logger configuration not found. |
/talq/log-reports/count [GET]
Request
curl "https://httpapi.sustainder.com/talq/log-reports/count" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"count": 1482
}
Returns the total number of log report entries across all loggers.
Response Fields
| Field | Type | Description |
|---|---|---|
| count | integer | Total number of log report entries. |
/talq/log-reports/{loggerAddress} [GET]
Request
curl "https://httpapi.sustainder.com/talq/log-reports/urn:talq:logger:l1a2b3c4-d5e6-7890-abcd-ef1234567890" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"logger_address": "urn:talq:logger:l1a2b3c4-d5e6-7890-abcd-ef1234567890",
"reports": [
{
"timestamp": "2024-11-15T14:00:00Z",
"values": [
{
"source": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activePower/value",
"value": 42.5,
"unit": "watt"
},
{
"source": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activeEnergy/value",
"value": 1285.7,
"unit": "kilowatt_hour"
}
]
},
{
"timestamp": "2024-11-15T14:15:00Z",
"values": [
{
"source": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activePower/value",
"value": 38.2,
"unit": "watt"
},
{
"source": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890/activeEnergy/value",
"value": 1286.3,
"unit": "kilowatt_hour"
}
]
}
],
"total_reports": 96
}
Returns the log reports collected for a specific logger configuration. Reports contain timestamped values for each configured source attribute.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| loggerAddress | string | The TALQ URN address of the logger configuration. |
Response Fields
| Field | Type | Description |
|---|---|---|
| logger_address | string | URN address of the logger this report belongs to. |
| reports | array | List of timestamped data entries. |
| reports[].timestamp | string | ISO 8601 timestamp of when the data was recorded. |
| reports[].values | array | List of recorded values at this timestamp. |
| reports[].values[].source | string | URN path of the source attribute. |
| reports[].values[].value | number | Recorded value. |
| reports[].values[].unit | string | Unit of measurement. |
| total_reports | integer | Total number of report entries available for this logger. |
Error Responses
| Status | Description |
|---|---|
| 404 | Logger configuration not found. |
TALQ CMS API
The TALQ CMS API handles inbound communications from TALQ-compliant gateways. In the TALQ architecture, the Sustainder Brokerage Layer (SBL) acts as the Central Management System (CMS) -- the central server that gateways register with and report to.
These endpoints receive registrations, device announcements, and service declarations from gateways. When a TALQ gateway connects to the SBL for the first time, it sends its device inventory, device class definitions, and service capabilities through these endpoints.
The base URL for the TALQ CMS API is https://httpapi.sustainder.com/talqcms.
This API communicates in JSON.
How Gateway Registration Works
The registration process follows the TALQ specification:
- Service announcement -- The gateway sends its service capabilities via POST /talqcms/services.
- Device class announcement -- The gateway announces the device classes it supports via POST /talqcms/device-classes.
- Device registration -- The gateway registers its devices via POST /talqcms/devices, initially using a zero UUID as the client address.
- Ongoing updates -- The gateway can update device details via PATCH /talqcms/devices/{deviceAddress} as device states change.
Authentication
All TALQ CMS API endpoints require JWT authentication. Include the token in the Authorization header:
Authorization: JWT {your_token}
TALQ CMS Devices
These endpoints handle device registration and management from TALQ gateways. Gateways use these endpoints to announce their devices to the CMS, update device information, and manage the device lifecycle.
/talqcms/devices [GET]
Request
curl "https://httpapi.sustainder.com/talqcms/devices" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"devices": [
{
"address": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"deviceClass": "LuminaireController",
"gateway_address": "urn:talq:gateway:550e8400-e29b-41d4-a716-446655440000",
"status": "operational",
"description": "Streetlight controller - Keizersgracht 42",
"registered_at": "2024-09-15T10:30:00Z"
},
{
"address": "urn:talq:device:b2c3d4e5-f6a7-8901-bcde-f12345678901",
"deviceClass": "LuminaireController",
"gateway_address": "urn:talq:gateway:550e8400-e29b-41d4-a716-446655440000",
"status": "operational",
"description": "Streetlight controller - Herengracht 108",
"registered_at": "2024-09-15T10:30:05Z"
}
],
"total": 2
}
Lists all devices that have been registered through the CMS interface by TALQ gateways.
Response Fields
| Field | Type | Description |
|---|---|---|
| address | string | Unique TALQ URN address of the device. |
| deviceClass | string | Name of the device class this device belongs to. |
| gateway_address | string | URN address of the gateway that registered this device. |
| status | string | Device status: operational, degraded, not_operational, or unknown. |
| description | string | Human-readable description of the device. |
| registered_at | string | ISO 8601 timestamp of when the device was registered with the CMS. |
| total | integer | Total number of registered CMS devices. |
/talqcms/devices [POST]
Request - Initial device registration (zero UUID)
curl -X POST "https://httpapi.sustainder.com/talqcms/devices?clientAddress=00000000-0000-0000-0000-000000000000" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"devices": [
{
"deviceClass": "LuminaireController",
"description": "Streetlight controller - Vondelpark East",
"functions": [
{
"functionId": "dimmingLevel",
"currentValue": 0,
"unit": "percent"
},
{
"functionId": "onOff",
"currentValue": false,
"unit": "boolean"
}
]
},
{
"deviceClass": "LuminaireController",
"description": "Streetlight controller - Vondelpark West",
"functions": [
{
"functionId": "dimmingLevel",
"currentValue": 0,
"unit": "percent"
},
{
"functionId": "onOff",
"currentValue": false,
"unit": "boolean"
}
]
}
]
}'
Request body
{
"devices": [
{
"deviceClass": "LuminaireController",
"description": "Streetlight controller - Vondelpark East",
"functions": [
{
"functionId": "dimmingLevel",
"currentValue": 0,
"unit": "percent"
},
{
"functionId": "onOff",
"currentValue": false,
"unit": "boolean"
}
]
},
{
"deviceClass": "LuminaireController",
"description": "Streetlight controller - Vondelpark West",
"functions": [
{
"functionId": "dimmingLevel",
"currentValue": 0,
"unit": "percent"
},
{
"functionId": "onOff",
"currentValue": false,
"unit": "boolean"
}
]
}
]
}
Response (201 Created)
{
"devices": [
{
"address": "urn:talq:device:c3d4e5f6-a7b8-9012-cdef-123456789012",
"deviceClass": "LuminaireController",
"gateway_address": "urn:talq:gateway:550e8400-e29b-41d4-a716-446655440000",
"status": "operational",
"description": "Streetlight controller - Vondelpark East",
"registered_at": "2024-11-15T15:00:00Z"
},
{
"address": "urn:talq:device:d4e5f6a7-b890-1234-cdef-234567890123",
"deviceClass": "LuminaireController",
"gateway_address": "urn:talq:gateway:550e8400-e29b-41d4-a716-446655440000",
"status": "operational",
"description": "Streetlight controller - Vondelpark West",
"registered_at": "2024-11-15T15:00:00Z"
}
]
}
Registers new devices with the CMS. This endpoint is called by TALQ gateways during the initial registration process.
For the initial registration, the clientAddress query parameter should be set to the zero UUID (00000000-0000-0000-0000-000000000000). The CMS will assign permanent URN addresses to each device and return them in the response.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| clientAddress | string | Yes | Client address for the registration. Use 00000000-0000-0000-0000-000000000000 for initial registration. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| devices | array | Yes | List of devices to register. |
| devices[].deviceClass | string | Yes | Device class name. Must match a previously announced device class. |
| devices[].description | string | No | Human-readable description of the device. |
| devices[].functions | array | No | Initial function values for the device. |
| devices[].functions[].functionId | string | Yes | Function identifier. |
| devices[].functions[].currentValue | mixed | Yes | Initial value of the function. |
| devices[].functions[].unit | string | No | Unit of measurement. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., unknown device class, missing required fields). |
/talqcms/devices/{deviceAddress} [GET]
Request
curl "https://httpapi.sustainder.com/talqcms/devices/urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (200 OK)
{
"address": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"deviceClass": "LuminaireController",
"gateway_address": "urn:talq:gateway:550e8400-e29b-41d4-a716-446655440000",
"status": "operational",
"description": "Streetlight controller - Keizersgracht 42",
"functions": [
{
"functionId": "dimmingLevel",
"currentValue": 75,
"unit": "percent"
},
{
"functionId": "onOff",
"currentValue": true,
"unit": "boolean"
}
],
"registered_at": "2024-09-15T10:30:00Z",
"last_updated": "2024-11-15T14:32:08Z"
}
Returns detailed information for a specific CMS-registered device.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| deviceAddress | string | The TALQ URN address of the device. |
Error Responses
| Status | Description |
|---|---|
| 404 | Device not found. |
/talqcms/devices/{deviceAddress} [PATCH]
Request
curl -X PATCH "https://httpapi.sustainder.com/talqcms/devices/urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"status": "degraded",
"functions": [
{
"functionId": "dimmingLevel",
"currentValue": 50,
"unit": "percent"
}
]
}'
Request body
{
"status": "degraded",
"functions": [
{
"functionId": "dimmingLevel",
"currentValue": 50,
"unit": "percent"
}
]
}
Response (200 OK)
{
"address": "urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"deviceClass": "LuminaireController",
"gateway_address": "urn:talq:gateway:550e8400-e29b-41d4-a716-446655440000",
"status": "degraded",
"description": "Streetlight controller - Keizersgracht 42",
"functions": [
{
"functionId": "dimmingLevel",
"currentValue": 50,
"unit": "percent"
},
{
"functionId": "onOff",
"currentValue": true,
"unit": "boolean"
}
],
"last_updated": "2024-11-15T15:10:00Z"
}
Updates information for a CMS-registered device. This is typically called by gateways to report status changes or updated function values.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| deviceAddress | string | The TALQ URN address of the device to update. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| status | string | No | Updated device status: operational, degraded, not_operational. |
| description | string | No | Updated device description. |
| functions | array | No | Updated function values. Only specified functions are updated; others remain unchanged. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error. |
| 404 | Device not found. |
/talqcms/devices/{deviceAddress} [DELETE]
Request
curl -X DELETE "https://httpapi.sustainder.com/talqcms/devices/urn:talq:device:a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Response (204 No Content)
204 No Content
Removes a device from the CMS registry. This is typically called when a device is decommissioned or removed from a gateway's network.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| deviceAddress | string | The TALQ URN address of the device to remove. |
Error Responses
| Status | Description |
|---|---|
| 404 | Device not found. |
TALQ CMS Device Classes
This endpoint receives device class announcements from TALQ gateways. When a gateway connects to the CMS, it announces the device classes it supports so that the CMS knows what types of devices to expect during registration.
/talqcms/device-classes [POST]
Request
curl -X POST "https://httpapi.sustainder.com/talqcms/device-classes" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"device_classes": [
{
"className": "LuminaireController",
"description": "TALQ luminaire controller with dimming and on/off capability",
"version": "2.4.0",
"functions": [
{
"functionId": "dimmingLevel",
"description": "Controls the dimming level of the luminaire",
"type": "numeric",
"writable": true,
"attributes": [
{
"attributeName": "targetLevel",
"type": "integer",
"unit": "percent",
"minValue": 0,
"maxValue": 100
},
{
"attributeName": "actualLevel",
"type": "integer",
"unit": "percent"
}
]
},
{
"functionId": "onOff",
"description": "On/Off control for the luminaire",
"type": "boolean",
"writable": true,
"attributes": [
{
"attributeName": "state",
"type": "boolean"
}
]
}
]
}
]
}'
Request body
{
"device_classes": [
{
"className": "LuminaireController",
"description": "TALQ luminaire controller with dimming and on/off capability",
"version": "2.4.0",
"functions": [
{
"functionId": "dimmingLevel",
"description": "Controls the dimming level of the luminaire",
"type": "numeric",
"writable": true,
"attributes": [
{
"attributeName": "targetLevel",
"type": "integer",
"unit": "percent",
"minValue": 0,
"maxValue": 100
},
{
"attributeName": "actualLevel",
"type": "integer",
"unit": "percent"
}
]
},
{
"functionId": "onOff",
"description": "On/Off control for the luminaire",
"type": "boolean",
"writable": true,
"attributes": [
{
"attributeName": "state",
"type": "boolean"
}
]
}
]
}
]
}
Response (201 Created)
{
"acknowledged": 1
}
Receives device class announcements from a TALQ gateway. The gateway sends its supported device class definitions so the CMS can validate subsequent device registrations.
If a device class with the same className already exists, it will be updated with the new definition.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| device_classes | array | Yes | List of device class definitions to announce. |
| device_classes[].className | string | Yes | Unique name of the device class. |
| device_classes[].description | string | No | Human-readable description. |
| device_classes[].version | string | Yes | TALQ protocol version of this device class definition. |
| device_classes[].functions | array | Yes | List of functions available on devices of this class. |
| device_classes[].functions[].functionId | string | Yes | Unique function identifier. |
| device_classes[].functions[].description | string | No | Description of the function. |
| device_classes[].functions[].type | string | Yes | Data type: numeric, boolean, string, enum. |
| device_classes[].functions[].writable | boolean | Yes | Whether the function can be controlled. |
| device_classes[].functions[].attributes | array | No | Attribute definitions for this function. |
Response Fields
| Field | Type | Description |
|---|---|---|
| acknowledged | integer | Number of device classes successfully processed. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., missing required fields, invalid function definitions). |
TALQ CMS Services
This endpoint receives service announcements from TALQ gateways. When a gateway connects to the CMS, it declares the services it is capable of providing.
/talqcms/services [POST]
Request
curl -X POST "https://httpapi.sustainder.com/talqcms/services" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"services": [
{
"serviceName": "LightingService",
"version": "2.4.0",
"description": "TALQ Lighting control service for outdoor luminaires",
"serviceType": "lighting",
"supported_functions": [
"dimmingLevel",
"onOff",
"colorTemperature"
],
"configuration": {
"max_devices": 1000,
"supports_multicast": true,
"supports_logging": true
}
},
{
"serviceName": "MeteringService",
"version": "2.4.0",
"description": "Energy metering service for power consumption tracking",
"serviceType": "metering",
"supported_functions": [
"activePower",
"activeEnergy",
"voltage"
],
"configuration": {
"max_devices": 1000,
"supports_multicast": false,
"supports_logging": true
}
}
]
}'
Request body
{
"services": [
{
"serviceName": "LightingService",
"version": "2.4.0",
"description": "TALQ Lighting control service for outdoor luminaires",
"serviceType": "lighting",
"supported_functions": [
"dimmingLevel",
"onOff",
"colorTemperature"
],
"configuration": {
"max_devices": 1000,
"supports_multicast": true,
"supports_logging": true
}
},
{
"serviceName": "MeteringService",
"version": "2.4.0",
"description": "Energy metering service for power consumption tracking",
"serviceType": "metering",
"supported_functions": [
"activePower",
"activeEnergy",
"voltage"
],
"configuration": {
"max_devices": 1000,
"supports_multicast": false,
"supports_logging": true
}
}
]
}
Response (201 Created)
201 Created
Receives service announcements from a TALQ gateway. Per the TALQ specification, the response body is empty with a 201 Created status code, indicating that the services have been acknowledged and registered.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| services | array | Yes | List of service definitions to announce. |
| services[].serviceName | string | Yes | Unique name of the service. |
| services[].version | string | Yes | TALQ protocol version supported by this service. |
| services[].description | string | No | Human-readable description. |
| services[].serviceType | string | Yes | Category of the service (e.g., lighting, metering, sensor). |
| services[].supported_functions | array | No | List of function names this service supports. |
| services[].configuration | object | No | Service-specific configuration parameters. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., missing required fields). |
Flitsmeister Integration
Flitsmeister is a Dutch traffic and navigation app. This integration allows Flitsmeister to send real-time event and traffic data to the Sustainder Brokerage Layer, which can then dynamically adjust street lighting in response to traffic conditions and incidents.
The base URL for the Flitsmeister API is https://httpapi.sustainder.com/vendor/flitsmeister.
/vendor/flitsmeister/events [POST]
Request - Ambulance event
curl -X POST "https://httpapi.sustainder.com/vendor/flitsmeister/events" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"timestamp": "2024-11-15T22:45:00Z",
"location_name": "Keizersgracht-Noord",
"event": "ambulance",
"phase": "start"
}'
Request body
{
"timestamp": "2024-11-15T22:45:00Z",
"location_name": "Keizersgracht-Noord",
"event": "ambulance",
"phase": "start"
}
Response (200 OK)
{
"status": "accepted",
"location_name": "Keizersgracht-Noord",
"event": "ambulance",
"phase": "start",
"override_duration_minutes": 10,
"affected_devices": 12
}
Request - Accident event
curl -X POST "https://httpapi.sustainder.com/vendor/flitsmeister/events" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"timestamp": "2024-11-15T23:15:00Z",
"location_name": "A10-Ring-Zuid",
"event": "accident",
"phase": "start"
}'
Request body (accident)
{
"timestamp": "2024-11-15T23:15:00Z",
"location_name": "A10-Ring-Zuid",
"event": "accident",
"phase": "start"
}
Response (200 OK)
{
"status": "accepted",
"location_name": "A10-Ring-Zuid",
"event": "accident",
"phase": "start",
"override_duration_minutes": 240,
"affected_devices": 48
}
Request - Stop event
curl -X POST "https://httpapi.sustainder.com/vendor/flitsmeister/events" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"timestamp": "2024-11-15T22:55:00Z",
"location_name": "Keizersgracht-Noord",
"event": "ambulance",
"phase": "stop"
}'
Request body (stop)
{
"timestamp": "2024-11-15T22:55:00Z",
"location_name": "Keizersgracht-Noord",
"event": "ambulance",
"phase": "stop"
}
Response (200 OK)
{
"status": "accepted",
"location_name": "Keizersgracht-Noord",
"event": "ambulance",
"phase": "stop",
"override_duration_minutes": 0,
"affected_devices": 12
}
Handles incident events from Flitsmeister. When an event starts, the SBL increases the lighting level to maximum brightness in the affected area for a duration that depends on the event type. When a stop event is received, the lighting override is cleared.
Event Durations
| Event Type | Override Duration | Description |
|---|---|---|
ambulance |
10 minutes | Ambulance or emergency vehicle in the area. |
park |
15 minutes | Park-related event or activity. |
accident |
240 minutes | Traffic accident requiring extended visibility. |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| timestamp | string | Yes | ISO 8601 timestamp of the event occurrence. |
| location_name | string | Yes | Name of the location where the event occurred. Must match a configured location in the SBL. |
| event | string | Yes | Type of event: ambulance, accident, or park. |
| phase | string | Yes | Event phase: start to begin the override, stop to end it. |
Response Fields
| Field | Type | Description |
|---|---|---|
| status | string | Processing status: accepted or rejected. |
| location_name | string | The location that was affected. |
| event | string | The event type that was processed. |
| phase | string | The event phase that was processed. |
| override_duration_minutes | integer | Duration of the lighting override in minutes. 0 when phase is stop. |
| affected_devices | integer | Number of devices affected by the event in this location. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., unknown event type, missing fields). |
| 404 | Location name not found in the SBL configuration. |
/vendor/flitsmeister/events/traffic [POST]
Request
curl -X POST "https://httpapi.sustainder.com/vendor/flitsmeister/events/traffic" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"timestamp": "2024-11-15T18:30:00Z",
"users": 145,
"avg_speed": 35.2,
"location": {
"latitude": 52.3702,
"longitude": 4.8879
}
}'
Request body
{
"timestamp": "2024-11-15T18:30:00Z",
"users": 145,
"avg_speed": 35.2,
"location": {
"latitude": 52.3702,
"longitude": 4.8879
}
}
Response (200 OK)
{
"status": "accepted",
"location": {
"latitude": 52.3702,
"longitude": 4.8879
},
"traffic_level": "high",
"light_level_applied": 100,
"affected_devices": 8
}
Request - Low traffic
curl -X POST "https://httpapi.sustainder.com/vendor/flitsmeister/events/traffic" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"timestamp": "2024-11-16T03:15:00Z",
"users": 8,
"avg_speed": 58.0,
"location": {
"latitude": 52.3702,
"longitude": 4.8879
}
}'
Request body (low traffic)
{
"timestamp": "2024-11-16T03:15:00Z",
"users": 8,
"avg_speed": 58.0,
"location": {
"latitude": 52.3702,
"longitude": 4.8879
}
}
Response (200 OK)
{
"status": "accepted",
"location": {
"latitude": 52.3702,
"longitude": 4.8879
},
"traffic_level": "low",
"light_level_applied": 50,
"affected_devices": 8
}
Handles traffic volume data from Flitsmeister. The SBL dynamically adjusts the light level of nearby devices based on the number of active users (traffic volume) in the area. Higher traffic volumes result in brighter lighting for improved safety; lower volumes allow energy-saving reduced lighting.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| timestamp | string | Yes | ISO 8601 timestamp of the traffic data reading. |
| users | integer | Yes | Number of active Flitsmeister users in the area, used as a proxy for traffic volume. |
| avg_speed | float | Yes | Average speed of users in the area (km/h). |
| location | object | Yes | GPS coordinates of the traffic data reading. |
| location.latitude | float | Yes | GPS latitude coordinate. |
| location.longitude | float | Yes | GPS longitude coordinate. |
Response Fields
| Field | Type | Description |
|---|---|---|
| status | string | Processing status: accepted or rejected. |
| location | object | The GPS coordinates that were matched. |
| traffic_level | string | Calculated traffic level: low, medium, or high. |
| light_level_applied | integer | The light level (0-100) applied to nearby devices based on traffic volume. |
| affected_devices | integer | Number of devices whose light level was adjusted. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., missing fields, invalid coordinates). |
| 404 | No devices found near the specified location. |
Intemo Integration
Intemo provides smart sensor solutions for outdoor environments. This integration allows Intemo systems to control the operating mode of compatible devices (LCMs) through the Sustainder Brokerage Layer.
The base URL for the Intemo API is https://httpapi.sustainder.com/vendor/intemo.
/vendor/intemo/set-mode [POST]
Request
curl -X POST "https://httpapi.sustainder.com/vendor/intemo/set-mode" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"lcm_ids": [
"LCM-001234",
"LCM-001235",
"LCM-001236"
],
"mode": "eco"
}'
Request body
{
"lcm_ids": [
"LCM-001234",
"LCM-001235",
"LCM-001236"
],
"mode": "eco"
}
Response (200 OK)
{
"success_device_ids": [
"LCM-001234",
"LCM-001235"
],
"failed_device_ids": [
"LCM-001236"
]
}
Request - Set to full brightness mode
curl -X POST "https://httpapi.sustainder.com/vendor/intemo/set-mode" \
-H "Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
-H "Content-Type: application/json" \
-d '{
"lcm_ids": [
"LCM-001234",
"LCM-001235"
],
"mode": "full"
}'
Request body (full mode)
{
"lcm_ids": [
"LCM-001234",
"LCM-001235"
],
"mode": "full"
}
Response (200 OK)
{
"success_device_ids": [
"LCM-001234",
"LCM-001235"
],
"failed_device_ids": []
}
Sets the operating mode for one or more Intemo-compatible devices (LCMs). The response indicates which devices were successfully updated and which failed.
A device may fail if it is offline, unreachable, or does not support the requested mode.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| lcm_ids | array | Yes | List of LCM identifiers (strings) to set the mode on. Must contain at least one entry. |
| mode | string | Yes | The operating mode to set. See available modes below. |
Available Modes
| Mode | Description |
|---|---|
eco |
Energy saving mode with reduced light output. |
full |
Full brightness mode. |
standby |
Standby mode with minimal power consumption. |
motion |
Motion-activated mode -- light increases when motion is detected. |
off |
Turns the device off completely. |
Response Fields
| Field | Type | Description |
|---|---|---|
| success_device_ids | array | List of LCM identifiers that were successfully updated. |
| failed_device_ids | array | List of LCM identifiers that could not be updated. |
Error Responses
| Status | Description |
|---|---|
| 400 | Validation error (e.g., empty lcm_ids array, unknown mode). |
Legacy API (V1) — Deprecated
The V1 API endpoints are no longer documented in this reference. If you are maintaining an existing V1 integration, please refer to the archived V1 documentation at v1.html.
Migration Guide
To migrate from V1 to V2:
- Authentication — V1 uses Basic Auth; V2 uses JWT tokens. See Authentication for details on obtaining and using JWT tokens.
- Response Format — V2 responses use HAL+JSON with
_linksand_embeddedfields. See Concepts for details. - Endpoints — V1 endpoints under
/api/have been replaced by V2 endpoints under/v2/. Consult the Endpoint Overview for the full list. - Pagination — V2 uses
pageandpageSizequery parameters with_totalin response metadata.