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.
- TALQ / TALQ CMS — TALQ-related endpoints are documented separately on the TALQ documentation page.
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. |
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.
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.