🏗️ Aquaticus — Projects API Guide

Everything you need to register & authenticate, create and manage clients & projects, and run calculations using ProjectVariables.

ℹ️ Email verification is required to:


🚀 Quick Start

1) Register

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

2) Login

curl -X POST https://api.aquaticus.tech/login \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","password":"StrongPass123!"}'

3) Verify your email (required for creating clients/projects and running calculations)

# 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"
Social Login

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)

You can find example flows how to use social login here

https://api.aquaticus.tech/__microsoft

https://api.aquaticus.tech/__google

https://api.aquaticus.tech/__facebook

https://api.aquaticus.tech/__apple


4) Create a client ➜ requires verified email

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

5) Create a project ➜ requires verified email & an existing clientId

curl -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
        }
      }'

6) Calculate from raw 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.json

7) Calculate for a saved project ➜ requires verified email

curl -X GET https://api.aquaticus.tech/calculate/{projectId} \
  -H "Authorization: Bearer <JWT_TOKEN>"

8) List projects

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

🧰 Tools — Temporary Variables API

Save and retrieve temporary ProjectVariables payloads for quick drafts or calculations.

Auth: JWT + verified email (same requirements as Projects & Calculations)


Endpoints

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

Get the latest temporary variable set

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


Save a temporary variables payload

Saves 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.json

Response (raw ProjectVariables):

{
  "shape": "rectangle",
  "length": 30.0,
  "width": 15.0,
  "depthProfileType": "standard",
  "shallowDepth": 3.5,
  "deepDepth": 8.0
}

🔐 Authentication & Email Confirmation

(unchanged content omitted for brevity — see above for full commands)


📁 Projects API

Endpoints

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

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:


👥 Clients API

Endpoints

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

Delete a client

Removes the client record.

Example:

curl -X DELETE https://api.aquaticus.tech/client/77 \
  -H "Authorization: Bearer <JWT_TOKEN>"

Response:


🧮 Calculations

Endpoints

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

Examples

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.json

Calculate for a saved project (requires verified email)

curl -X GET https://api.aquaticus.tech/calculate/{projectId} \
  -H "Authorization: Bearer <JWT_TOKEN>"

🧱 ProjectVariables: Fields, Defaults & Validation

(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.


👤 Me — User Profile API

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.)

Endpoints

Method Path Auth Description
GET /me JWT required Get the current user’s profile
PATCH /me JWT required Update the current user’s profile

Get my 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


Update my profile (partial)

All 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

Response

Errors


✅ Validation ranges

ℹ️ These ranges apply to /me PATCH fields where relevant (name, lastName follows Name, companyName, companyAddress, role).

✅ Validation ranges


⚠️ Notes & errors


📄 Pagination

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.

🔑 API keys

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.

Managing keys — /api-keys

Owner 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 pending changes — /ai/changes

The 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.