MENU navbar-image

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."
        ]
    }
}
 

Request      

GET app_api/clients

Query Parameters

limit  number optional  

Number of clients per page. Defaults to 10.

page  number optional  

Page number. Defaults to 1.

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"
}
 

Request      

GET app_api/clients/{client}/details

URL Parameters

client  integer  

The ID of the client.

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."
        ]
    }
}
 

Request      

GET app_api/controlboxes/{controlbox}/cycles

URL Parameters

controlbox  integer  

The ID of the control box.

Query Parameters

sincedate  string  

Only cycles logged after this date (Y-m-d) are returned. Must be a valid date in the format Y-m-d.

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"
}
 

Request      

GET app_api/me

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.