Everything you need to register & authenticate,
create and manage clients & projects, and
run calculations using
ProjectVariables.
ℹ️ Email verification is required to:
- Create clients (
POST /client)- Create projects (
POST /project) — requires an existingclientId- Run calculations (
POST /calculate,GET /calculate/{projectId})
curl -X POST https://api.aquaticus.tech/register \
-H "Content-Type: application/json" \
-d '{
"email":"user@example.com",
"password":"StrongPass123!"
}'Response
{ "token": "JWT_TOKEN" }Defaults applied
isEmailVerified is false until
confirmed (or social login succeeds).curl -X POST https://api.aquaticus.tech/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"StrongPass123!"}'# Send confirmation code
curl -X POST https://api.aquaticus.tech/sendEmailConfirmationCode \
-H "Authorization: Bearer JWT_TOKEN"
# Confirm
curl -X POST https://api.aquaticus.tech/confirmEmail \
-H "Authorization: Bearer JWT_TOKEN" \
-H "Content-Type: text/plain" \
-d "123456"Using social login is an alternative way to verify user’s email. After any social login is done successfully the email is marked verified
curl -X POST https://api.aquaticus.tech/login/google \
-H "Content-Type: text/plain" \
-d "SOCIAL_ACCESS_TOKEN"(Replace google with facebook,
apple, or microsoft)
https://api.aquaticus.tech/__microsoft
https://api.aquaticus.tech/__google
https://api.aquaticus.tech/__facebook
https://api.aquaticus.tech/__apple
curl -X POST https://api.aquaticus.tech/client \
-H "Authorization: Bearer <JWT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"email": "client@example.com",
"name": "Acme Resorts",
"phoneNumber": "+1 555 222 3333",
"address": "456 Resort Way, San Diego, CA"
}'Response
{
"id": 77,
"email": "client@example.com",
"name": "Acme Resorts",
"phoneNumber": "+1 555 222 3333",
"address": "456 Resort Way, San Diego, CA"
}clientIdcurl -X POST https://api.aquaticus.tech/project \
-H "Authorization: Bearer <JWT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "Sunset Villas – Pool A",
"clientId": 77,
"variables": {
"shape": "rectangle",
"length": 30.0,
"width": 15.0,
"finishThickness": 0.5,
"depthProfileType": "standard",
"shallowDepth": 3.5,
"deepDepth": 8.0,
"shallowLengthPercentage": 30.0,
"wallThickness": 8.0,
"floorThickness": 6.0,
"beamHeight": 6.0,
"beamThickness": 12.0,
"gravelThickness": 6.0,
"steelPatternWall": "#4 @ 12\" OC",
"steelPatternFloor": "#4 @ 12\" OC",
"hasSteps": false,
"hasBench": false,
"turnoverHours": 6.0,
"minVelocitySuction": 3.0,
"maxVelocitySuction": 5.0,
"minVelocityReturn": 6.0,
"maxVelocityReturn": 8.0,
"pipeRunLength": 30.0
}
}'curl -X POST https://api.aquaticus.tech/calculate \
-H "Authorization: Bearer <JWT_TOKEN>" \
-H "Content-Type: application/json" \
--data-binary @project-variables.jsoncurl -X GET https://api.aquaticus.tech/calculate/{projectId} \
-H "Authorization: Bearer <JWT_TOKEN>"# All my projects
curl -X GET https://api.aquaticus.tech/projects \
-H "Authorization: Bearer <JWT_TOKEN>"
# Projects for a client
curl -X GET https://api.aquaticus.tech/projects/{clientId} \
-H "Authorization: Bearer <JWT_TOKEN>"Save and retrieve temporary
ProjectVariables payloads for quick drafts or
calculations.
Auth: JWT + verified email (same requirements as Projects & Calculations)
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /tools |
JWT + verified email | Get the latest temporary variables (raw JSON) |
| POST | /tools |
JWT + verified email | Save a temporary ProjectVariables payload |
Returns only the variables object
itself. No wrapper. No id. No timestamps.
curl -X GET https://api.aquaticus.tech/tools \
-H "Authorization: Bearer <JWT_TOKEN>"Response (raw ProjectVariables):
{
"shape": "rectangle",
"length": 30.0,
"width": 15.0,
"finishThickness": 0.5,
"depthProfileType": "standard",
"shallowDepth": 3.5,
"deepDepth": 8.0,
"shallowLengthPercentage": 30.0,
"wallThickness": 8.0,
"floorThickness": 6.0,
"beamHeight": 12.0,
"beamThickness": 10.0,
"gravelThickness": 6.0,
"steelPatternWall": "#4 @ 12\" OC",
"steelPatternFloor": "#4 @ 12\" OC",
"hasSteps": false,
"hasBench": false,
"turnoverHours": 6.0,
"minVelocitySuction": 3.0,
"maxVelocitySuction": 5.0,
"minVelocityReturn": 6.0,
"maxVelocityReturn": 8.0,
"pipeRunLength": 30.0
}Errors
401 Unauthorized403 Forbidden — email not verified404 Not Found — no stored variables yetSaves a new set of ProjectVariables. No wrapper in the
response.
curl -X POST https://api.aquaticus.tech/tools \
-H "Authorization: Bearer <JWT_TOKEN>" \
-H "Content-Type: application/json" \
--data-binary @project-variables.jsonResponse (raw ProjectVariables):
{
"shape": "rectangle",
"length": 30.0,
"width": 15.0,
"depthProfileType": "standard",
"shallowDepth": 3.5,
"deepDepth": 8.0
}(unchanged content omitted for brevity — see above for full commands)
| Method | Path | Auth | Notes |
|---|---|---|---|
| GET | /projects |
JWT + verified email | List my projects |
| GET | /projects/{clientId} |
JWT + verified email | List my projects for a given client |
| GET | /project/{id} |
JWT + verified email | Get one project |
| POST | /project |
JWT + verified email | Create a project (requires
clientId) |
| PATCH | /project/{id} |
JWT + verified email | Update a project (partial) |
| DELETE | /project/{id} |
JWT + verified email | Delete a project |
Removes the project (and its variables) permanently.
Example:
curl -X DELETE https://api.aquaticus.tech/project/123 \
-H "Authorization: Bearer <JWT_TOKEN>"Response:
204 No Content on success404 Not Found if the project does not exist or doesn’t
belong to you| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /clients |
JWT + verified email | List my clients — ?search= (name or email),
?limit=, ?offset= |
| GET | /client/{id} |
JWT + verified email | Get one client |
| POST | /client |
JWT + verified email | Create client |
| PATCH | /client/{id} |
JWT + verified email | Update client (partial) |
| DELETE | /client/{id} |
JWT + verified email | Delete client |
Removes the client record.
Example:
curl -X DELETE https://api.aquaticus.tech/client/77 \
-H "Authorization: Bearer <JWT_TOKEN>"Response:
204 No Content on success404 Not Found if the client does not exist or doesn’t
belong to you409 Conflict if the client has existing projects| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /formulas/js |
JWT required | Download the JavaScript formulas used by apps |
| POST | /calculate |
JWT + verified email | Calculate from raw ProjectVariables |
| GET | /calculate/{projectId} |
JWT + verified email | Calculate using a saved project’s variables |
Get formulas JS
curl -X GET https://api.aquaticus.tech/formulas/js \
-H "Authorization: Bearer <JWT_TOKEN>"Calculate from variables (requires verified email)
curl -X POST https://api.aquaticus.tech/calculate \
-H "Authorization: Bearer <JWT_TOKEN>" \
-H "Content-Type: application/json" \
--data-binary @project-variables.jsonCalculate for a saved project (requires verified email)
curl -X GET https://api.aquaticus.tech/calculate/{projectId} \
-H "Authorization: Bearer <JWT_TOKEN>"(unchanged; includes field list, shape rules, server defaults, UI defaults, and example payload)
Awesome—here’s a drop-in section for your guide that documents the /me endpoints in the same style as the rest of your API docs.
Manage the authenticated user’s profile. Auth: JWT required. (Email verification is not required for these endpoints, but it is still required for creating clients/projects and running calculations.)
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /me |
JWT required | Get the current user’s profile |
| PATCH | /me |
JWT required | Update the current user’s profile |
curl -X GET https://api.aquaticus.tech/me \
-H "Authorization: Bearer <JWT_TOKEN>"Response
{
"id": 123,
"email": "user@example.com",
"isEmailVerified": true,
"name": "Alex",
"lastName": "Johnson",
"companyName": "Acme Resorts",
"companyAddress": "456 Resort Way, San Diego, CA",
"phoneNumber": "+1 555 222 3333",
"role": "Project Manager"
}Errors
401 Unauthorized — login againAll fields are optional and will be trimmed. Validation uses the ranges defined below (see ✅ Validation ranges).
curl -X PATCH https://api.aquaticus.tech/me \
-H "Authorization: Bearer <JWT_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"name": "Alex",
"lastName": "Johnson",
"companyName": "Acme Resorts",
"companyAddress": "456 Resort Way, San Diego, CA",
"phoneNumber": "+1 555 222 3333",
"role": "Project Manager"
}'Body schema
{
"name": "string | null",
"lastName": "string | null",
"companyName": "string | null",
"companyAddress": "string | null",
"phoneNumber": "string | null",
"role": "string | null"
}Notes
email cannot be changed here.Response
200 OK with an empty body on success
(implementation returns no content)Errors
400 Bad Request — one or more fields fail
validation404 Not Found — user does not existℹ️ These ranges apply to
/mePATCH fields where relevant (name,lastNamefollows Name,companyName,companyAddress,role).
Every list endpoint accepts ?limit= (how many to return,
at least 1) and ?offset= (how many to skip, default 0),
independently of each other — see routing/Pagination.kt.
Without limit a list returns everything, except
GET /estimates (default 50) and GET /inventory
(default 100). Bad values fall back (limit below 1 becomes
1, a negative or non-numeric offset becomes 0). A page
shorter than limit is the last one.
Besides a session JWT, every authenticate { } route
accepts an API key as the bearer token. Keys are minted
in the app (Settings → API Keys) by the account owner or an Admin
member, shown once, and stored only as a SHA-256 hash. A key acts as the
account owner (so ownership scoping and member checks treat it as the
owner) and is additionally limited to the sections it was granted — see
com.aquaticus.apikey.
curl https://api.aquaticus.tech/projects \
-H "Authorization: Bearer aq_YOUR_KEY"| Section | Paths |
|---|---|
| Projects and Model | /projects, /project/…,
/calculate…, /formulas/js,
/formulas/jscode, /tags, /tag/…,
/tools, /spa-presets… |
| Clients | /clients, /client/… |
| Tax and Markup Profiles | /tax-profiles…, /markup-presets… |
| Estimates and Templates | /estimates…, /templates…,
/sections/…, /line-items/…,
/estimate-tags…, /formulas/validate,
/formulas/variables |
| Inventory | /inventory…, /suppliers… |
The action is derived from the method: GET → view,
POST/PUT/PATCH → edit,
DELETE → delete, except POST /calculate and
POST /formulas/validate, which only compute and count as
view. Edit or delete implies view, and view on estimates implies view on
tax/markup profiles and inventory
(ApiKeyPermissions.normalized). Anything outside those
sections — /me, /members,
/settings, /company-settings,
/api-keys, /logout, /ai/changes —
answers 403 ApiKeyEndpointForbidden to a key; a missing
permission answers 403 ApiKeyPermissionDenied naming what
is missing. An unknown key is a plain 401.
The consumer-facing reference lives on the landing site: https://aquaticus.tech/api-documentation.
/api-keysOwner or Admin only (403 Forbidden otherwise), verified
email required, and never reachable with an API key itself.
| Method | Path | Description |
|---|---|---|
| GET | /api-keys |
The account’s keys, newest first (id,
name, keyPrefix, permissions,
createdAt, lastUsedAt,
createdByUserId, createdByEmail) |
| POST | /api-keys |
{name, permissions} → the same object plus
key, the secret, returned this once. 400
InvalidName / NoPermissions,
409 at 50 keys per account |
| DELETE | /api-keys/{id} |
{"success": true}; 404 when it isn’t
this account’s key |
permissions is
{projects, clients, taxMarkup, estimates, inventory}, each
{view, edit, delete}; every flag is always present in
responses.
/ai/changesThe AI assistant service never calls a write endpoint. It proposes a change here; this API validates it, stores it, and applies it when the user approves. Moving the gate into the backend is what makes the assistant correctable: a body that would have been refused at write time is refused at propose time, before the user is asked to approve anything.
| Method | Path | Description |
|---|---|---|
| POST | /ai/changes |
Propose a change — {chatId, action, payload}.
400 if the payload isn’t a valid body for
action. |
| GET | /ai/changes |
Pending changes for the caller (?chatId=,
?status=) |
| GET | /ai/changes/{id} |
One change; ?waitSeconds=N (max 30) long-polls until
it’s answered |
| POST | /ai/changes/{id}/approve |
Approve and apply. Optional {params}:
fields the user corrected on the card |
| POST | /ai/changes/{id}/reject |
Refuse it (optional {reason}) |
| POST | /ai/changes/{id}/retry |
Put an answered change back in front of the user, unchanged |
| POST | /ai/changes/cancel |
Reject every pending change in a chat —
{chatId, reason} |
All of them require a verified email, and act only on the caller’s own changes.
action is one of ChangeAction
(create_project, update_project,
delete_project, create_client,
create_estimate, update_estimate,
set_estimate_status, add_section,
add_line_item, update_line_item,
delete_section, delete_line_item,
create_template, update_settings) — that enum
is the whole allow-list. payload is the body the
corresponding endpoint takes, with any path parameters folded in as
fields (projectId, sectionId, …).
A 400 from POST /ai/changes names what would have been
valid rather than just what was wrong:
{
"error": "BadJsonBody",
"description": "'insidePool' is not a valid SpaAttachment. Valid values: interior, freestanding."
}Adding an action means adding a payload type
(ChangePayload) and an apply branch
(ChangeApplier); there is deliberately no generic “forward
this body to that path” path. ChangeApplier goes through
the same repository calls the REST routes make, so an approved change
lands exactly as the equivalent action in the app would — permissions,
project history and all.