Introduction
API for the Acquanet app, authenticated with a per-user API key.
This documentation aims to provide all the information you need to work with our API.
Base URL
http://acquanet.test
Authenticating requests
Authenticate requests to this API's endpoints by sending a X-API-Key header with the value "{YOUR_API_KEY}".
All authenticated endpoints are marked with a requires authentication badge in the documentation below.
API keys are created by a superuser under Admin → API keys. Every request is handled as the user the key belongs to.
Clients
List clients
requires authentication
Returns the user's own client and all of its child clients that have control boxes, each with its control boxes (masters) and their slaves.
Example request:
curl --request GET \
--get "http://acquanet.test/app_api/clients?limit=10&page=1" \
--header "X-API-Key: {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"http://acquanet.test/app_api/clients"
);
const params = {
"limit": "10",
"page": "1",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"X-API-Key": "{YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"page": 1,
"limit": 10,
"total": 1,
"clients": [
{
"id": 2,
"name": "Meridian Swimming Pool",
"masters": [
{
"id": 1,
"name": "Women's Locker Room",
"ip": "",
"firmware": "1.14",
"online": true,
"last_contact": "2026-10-03T06:38:45.000000Z",
"slave_count": 2,
"slaves": [
{
"id": 1,
"name": "Shower 1",
"temperature": 21.7,
"type": "shower"
},
{
"id": 2,
"name": "Shower 2",
"temperature": 38.5,
"type": "shower"
}
],
"last_syncs": {
"eventlogs": "2026-10-01T05:30:00.000000Z",
"hot_cycle": "2026-10-01T05:30:00.000000Z",
"mixed_cycle": null,
"settings": "2026-10-03T06:42:45.000000Z"
}
}
],
"online": true
}
]
}
Example response (401):
{
"message": "Unauthorized"
}
Example response (422):
{
"message": "The given data was invalid.",
"errors": {
"page": [
"The page must be a number."
]
}
}
Received response:
Request failed with error:
Response
Response Fields
page integer
The requested page.
limit integer
The requested page size.
total integer
Number of clients the user can access.
clients object[]
The clients the user can access.
clients[].id integer
Client ID.
clients[].name string
Client name.
clients[].online boolean
Whether the client is online.
clients[].masters object[]
The client's control boxes.
clients[].masters[].id integer
Control box ID.
clients[].masters[].name string
Control box alias.
clients[].masters[].ip string
IP address of the control box (currently always empty).
clients[].masters[].firmware string
Firmware version of the control box.
clients[].masters[].online boolean
Whether the control box is online.
clients[].masters[].last_contact string
ISO 8601 time of the last check-in, or null if it never checked in.
clients[].masters[].slave_count integer
Number of slaves on the control box.
clients[].masters[].slaves object[]
The control box's slaves.
clients[].masters[].slaves[].id integer
Slave ID.
clients[].masters[].slaves[].name string
Slave alias.
clients[].masters[].slaves[].temperature number
Last measured temperature in °C.
clients[].masters[].slaves[].type string
One of: generic, shower, group shower, faucet, urinal.
clients[].masters[].last_syncs object
ISO 8601 times of the last received data per type; null if never received.
clients[].masters[].last_syncs.eventlogs string
Last event log.
clients[].masters[].last_syncs.hot_cycle string
Last hot cycle temperature log.
clients[].masters[].last_syncs.mixed_cycle string
Last mixed cycle temperature log.
clients[].masters[].last_syncs.settings string
Last settings update.
Client details
requires authentication
Returns a single client the user has access to, with its control boxes (masters) and their slaves.
Example request:
curl --request GET \
--get "http://acquanet.test/app_api/clients/2/details" \
--header "X-API-Key: {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"http://acquanet.test/app_api/clients/2/details"
);
const headers = {
"X-API-Key": "{YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"id": 2,
"name": "Meridian Swimming Pool",
"masters": [
{
"id": 1,
"name": "Women's Locker Room",
"ip": "",
"firmware": "1.14",
"online": true,
"last_contact": "2026-10-03T06:38:45.000000Z",
"slave_count": 2,
"slaves": [
{
"id": 1,
"name": "Shower 1",
"temperature": 21.7,
"type": "shower"
},
{
"id": 2,
"name": "Shower 2",
"temperature": 38.5,
"type": "shower"
}
],
"last_syncs": {
"eventlogs": "2026-10-01T05:30:00.000000Z",
"hot_cycle": "2026-10-01T05:30:00.000000Z",
"mixed_cycle": null,
"settings": "2026-10-03T06:42:45.000000Z"
}
}
],
"online": true
}
Example response (401):
{
"message": "Unauthorized"
}
Example response (404):
{
"message": "Not Found"
}
Received response:
Request failed with error:
Response
Response Fields
id integer
Client ID.
name string
Client name.
online boolean
Whether the client is online.
masters object[]
The client's control boxes. Each has the same fields as in "List clients".
Control boxes
Control box cycles
requires authentication
Returns the temperature cycles a control box logged after the given date, with the result per shower.
Example request:
curl --request GET \
--get "http://acquanet.test/app_api/controlboxes/1/cycles?sincedate=2026-10-03" \
--header "X-API-Key: {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"http://acquanet.test/app_api/controlboxes/1/cycles"
);
const params = {
"sincedate": "2026-10-03",
};
Object.keys(params)
.forEach(key => url.searchParams.append(key, params[key]));
const headers = {
"X-API-Key": "{YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
[
{
"id": 1834,
"start_date": "2026-10-03T07:30:00.000000Z",
"end_date": "2026-10-03T07:30:59.000000Z",
"sync_date": "2026-10-03T05:30:00.000000Z",
"successful": false,
"cycle_conclusions": [
{
"name": "Shower 1",
"success": true
},
{
"name": "Shower 2",
"success": false
}
],
"is_mix_cycle": false,
"cycle_water_usage": {
" Shower0-1": {
"name": "Shower 1",
"usage": 8.36
},
" Shower1-2": {
"name": "Shower 2",
"usage": 8.36
}
},
"shower_data": [
{
"name": "Shower0",
"start_date": "2026-10-03 07:30:00 UTC",
"end_date": "2026-10-03 07:30:59 UTC",
"seconds_over_60": 11,
"seconds_over_65": 0,
"seconds_over_70": 0,
"highest_temp": "63.8"
},
{
"name": "Shower1",
"start_date": "2026-10-03 07:30:00 UTC",
"end_date": "2026-10-03 07:30:59 UTC",
"seconds_over_60": 0,
"seconds_over_65": 0,
"seconds_over_70": 0,
"highest_temp": "41.2"
}
]
}
]
Example response (401):
{
"message": "Unauthorized"
}
Example response (404):
{
"message": "Not Found"
}
Example response (422):
{
"message": "The given data was invalid.",
"errors": {
"sincedate": [
"The sincedate does not match the format Y-m-d."
]
}
}
Received response:
Request failed with error:
Response
Response Fields
id integer
ID of the temperature log the cycle was read from.
start_date string
ISO 8601 time of the first measurement in the cycle.
end_date string
ISO 8601 time of the last measurement in the cycle.
sync_date string
ISO 8601 time the control box sent the log.
successful boolean
Whether every shower reached the required temperature.
cycle_conclusions object[]
Result per shower.
cycle_conclusions[].name string
Shower name.
cycle_conclusions[].success boolean
Whether this shower reached the required temperature.
is_mix_cycle boolean
Whether this was a mixed (cold/warm) cycle instead of a hot cycle.
cycle_water_usage object
Water usage per shower in liters, keyed by the shower's internal column name.
shower_data object[]
Temperature statistics per shower.
shower_data[].name string
Internal shower name as sent by the control box.
shower_data[].start_date string
Time of the shower's first measurement ("Y-m-d H:i:s UTC").
shower_data[].end_date string
Time of the shower's last measurement ("Y-m-d H:i:s UTC").
shower_data[].seconds_over_60 integer
Seconds the shower was above 60 °C.
shower_data[].seconds_over_65 integer
Seconds the shower was above 65 °C.
shower_data[].seconds_over_70 integer
Seconds the shower was above 70 °C.
shower_data[].highest_temp string
Highest measured temperature in °C.
Users
Current user
requires authentication
Returns the user the API key belongs to.
Example request:
curl --request GET \
--get "http://acquanet.test/app_api/me" \
--header "X-API-Key: {YOUR_API_KEY}" \
--header "Content-Type: application/json" \
--header "Accept: application/json"const url = new URL(
"http://acquanet.test/app_api/me"
);
const headers = {
"X-API-Key": "{YOUR_API_KEY}",
"Content-Type": "application/json",
"Accept": "application/json",
};
fetch(url, {
method: "GET",
headers,
}).then(response => response.json());Example response (200):
{
"id": 5,
"name": "Emma Clarke",
"email": "pool.manager@example.com",
"role_id": 3,
"email_verified_at": null,
"created_at": "2026-08-05T06:42:45.000000Z",
"updated_at": "2026-08-05T06:42:45.000000Z",
"client_id": 2,
"must_change_password": 0,
"language": "en",
"receive_notifications": false,
"isDeveloper": 0,
"login_driver": "db",
"hasPwaInstalled": 0,
"lastTimePwaPopupShown": null
}
Example response (401):
{
"message": "Unauthorized"
}
Received response:
Request failed with error:
Response
Response Fields
id integer
User ID.
name string
Full name.
email string
Email address.
role_id integer
ID of the user's role.
client_id integer
ID of the client the user belongs to.
language string
Preferred language code, e.g. en or nl.
receive_notifications boolean
Whether the user receives notification emails.