🏊 Personalized Pool – Quiz Guide

Everything you need to create, upload, answer, and verify quizzes on the Personalized Pool platform.


🚀 1 · Quick Start

👩‍💼 Admin: Upload a Quiz

curl -X POST https://api.personalizedpool.com/quiz/{quizId} \
     -H "Authorization: Bearer <ADMIN_JWT>" \
     -H "Content-Type: application/json" \
     --data-binary @schema.json

👩‍💼 Admin: Update existing Quiz

curl -X PATCH https://api.personalizedpool.com/quiz/{quizId} \
     -H "Authorization: Bearer <ADMIN_JWT>" \
     -H "Content-Type: application/json" \
     --data-binary @schema.json

👤 User: Read a Quiz

curl -H "Authorization: Bearer <USER_JWT>" \
     https://api.personalizedpool.com/quiz/{quizId}

👤 User: Get list of quiz

curl -X GET https://api.personalizedpool.com/quizList \
     -H "Authorization: Bearer <USER_JWT>"

📝 User: Submit Answers

curl -X POST https://api.personalizedpool.com/answers/{quizId} \
     -H "Authorization: Bearer <USER_JWT>" \
     -H "Content-Type: application/json" \
     --data-binary @answers.json

📄 User: Read Your Previous Answers

curl -X GET https://api.personalizedpool.com/answers/{quizId} \
     -H "Authorization: Bearer <USER_JWT>"

🔒 Only admins can upload or delete quizzes.


🔐 2 · User Authentication & Email Confirmation

🧾 Register and Confirm Email

1️⃣ Register
curl -X POST http://api.personalizedpool.com/register \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "password": "yourPassword123"}'

Response:

{ "token": "JWT_TOKEN" }
2️⃣ Send Confirmation Code
curl -X POST http://api.personalizedpool.com/sendEmailConfirmationCode \
  -H "Authorization: Bearer JWT_TOKEN"
3️⃣ Confirm Email
curl -X POST http://api.personalizedpool.com/confirmEmail \
  -H "Authorization: Bearer JWT_TOKEN" \
  -H "Content-Type: text/plain" \
  -d "123456"
4️⃣ Check Verification Status
curl -X GET http://api.personalizedpool.com/me \
  -H "Authorization: Bearer JWT_TOKEN"

Expected:

{
  "email": "user@example.com",
  "isEmailVerified": true
}

🔄 Change Password (with Email Confirmation)

Changing your password requires receiving and confirming a code. A password should be at least 8 characters long.

1️⃣ Send Reset Code
curl -X POST http://api.personalizedpool.com/sendEmailConfirmationCodeForPassword \
  -H "Content-Type: text/plain" \
  -d "user@example.com"
2️⃣ ✅ Optionally Check Code Before Proceeding
curl -X POST http://api.personalizedpool.com/checkEmailConfirmationCodeForPassword/user@example.com \
  -H "Content-Type: text/plain" \
  -d "123456"

Response on success:

{
  "message": "Code is valid. You can proceed with password change."
}

If the code is invalid, it is deleted and the response is 400 Bad Request.

3️⃣ Change Password
curl -X POST http://api.personalizedpool.com/changePassword \
  -H "Content-Type: application/json" \
  -d '{
        "code": "123456",
        "newPassword": "NewPass123!",
        "email": "user@example.com"
      }'
4️⃣ Re-Login
curl -X POST http://api.personalizedpool.com/login \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "password": "NewPass123!"}'

🔐 Other Auth Endpoints

Login
curl -X POST http://api.personalizedpool.com/login \
  -H "Content-Type: application/json" \
  -d '{"email": "user@example.com", "password": "yourPassword123"}'
Logout
curl -X POST http://api.personalizedpool.com/logout \
  -H "Authorization: Bearer JWT_TOKEN"
Get Current User
curl -X GET http://api.personalizedpool.com/me \
  -H "Authorization: Bearer JWT_TOKEN"
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 http://api.personalizedpool.com/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.personalizedpool.com/__microsoft

https://api.personalizedpool.com/__google

https://api.personalizedpool.com/__facebook

https://api.personalizedpool.com/__apple


🧠 3 · Schema Tutorial

A quiz schema is a JSON object where each key is a question ID and each value defines how that question behaves.


✅ Question Type Behavior

Type Description Navigation Keys
text Short text input with optional validation regex and maxLength. next
volume Pool size input in "45000l" or "12000g" format. next
season Seasonal range using "M/W-M/W" format (e.g. "5/1-9/1" → May week 1 to Sept week 1). next
bool Yes/No toggle. Branches to two different questions. nextTrue, nextFalse
radio Choose one option. Each option may route to a different question. Per-option next
radioWithOther Like radio, but includes "Other" with free text and a separate nextOther route. Per-option next, nextOther
checkbox Choose multiple answers. All answers follow the same path. next
checkboxWithSkip Like checkbox, but allows skipping the question (null value). next, nextSkip
optionalEnd Offers the user to either continue with optional questions or end. nextContinue, nextSkip
email Final step to enter the user’s email address. next (usually null)
error Displays a stopping message (e.g. unsupported configuration) and ends the quiz. next: null

🧪 4 · Answer Format Guide

Answers is a key-value JSON, where key is the question ID inside quiz schema. Each answer in the quiz must match the type of the question.

Type Example Answer Notes
text "Alice" Can be validated via regex
volume "45000l" or "12000g" Must include unit suffix (l or g)
season "5/1-9/1" Format: "M/W-M/W" where M = month, W = week in month
bool true or false Branches conditionally
radio "Chlorine" Option must exist in schema
radioWithOther { "value": "Other", "other": "Ionization" } other is Required if value is "Other"
checkbox ["Option A", "Option B"] Select multiple
checkboxWithSkip null or ["Option A"] Use null to skip question
optionalEnd "continue" or "skip" Branches to next or exits
email "user@example.com" Required to receive results
error (no answer — this ends the quiz)

Example Answers JSON

{
  "root": "Alice",
  "poolVolume": "45000l",
  "seasonLength": "My pool is seasonal",
  "seasonalPool": "1/1-6/1",
  "isYourPoolGetClosed": true,
  "primarySanitizer": "Chlorine",
  "chlorineType": ["Liquid chlorine"],
  "typeOfPool": "Vinyl Liner",
  "inGroundOrAboveGround": "In-ground",
  "automaticCover": true,
  "hasHotSystem": false,
  "genericQuizPassed": "continue",
  "hasSecondarySanitizer": "Yes",
  "secondarySanitizer": ["UV", "Ozone"],
  "typeOfFilter": "Sand/crushed glass",
  "filterBrand": { "value": "Other", "other": "BlueWaterPro" },
  "typeOfPump": "Variable-speed",
  "pumpBrand": { "value": "Pentair", "other": null },
  "circulateHours": "16 or more hours",
  "automation": "Yes",
  "automationType": ["Full automation (phone app)"],
  "automationBrand": { "value": "Intellicenter® (Pentair)", "other": null },
  "dogsInPool": false,
  "email": "alice@example.com"
}

🌐 5 · API Reference

📋 Quiz

Method Path Auth Description
GET /quiz/{quizId} Confirmed email Fetch quiz schema
POST /quiz/{quizId} Admin only Upload new quiz
PATCH /quiz/{quizId} Admin only Update existing quiz
DELETE /quiz/{quizId} Admin only Delete quiz
GET /quizList Confirmed email Get available quiz list

📨 Answers

Method Path Auth Description
GET /answers/{quizId} Confirmed email Fetch user’s answers and the suggested kit
POST /answers/{quizId} Confirmed email Submit new answers

👤 User

Method Path Auth Description
POST /register None Register a new user
POST /login None Login with email and password
POST /logout JWT required Logout and invalidate current session
GET /me JWT required Get current user info
POST /sendEmailConfirmationCode JWT required Send email confirmation code
POST /confirmEmail JWT required Submit confirmation code
POST /sendEmailConfirmationCodeForPassword None Send password reset code
POST /checkEmailConfirmationCodeForPassword/{email} None ✅ Check if reset code is valid
POST /changePassword None Change password with email and code
POST /login/{provider} None Social login (google, facebook, etc.)

🧰 Kit Matrix (Admin)

Method Path Auth Description
GET /kit/matrix Admin Export current kit matrix as CSV
POST /kit/matrix Admin Upload new kit matrix CSV

Example:

🔁 Export Kit Matrix
curl -X GET https://api.personalizedpool.com/kit/matrix \
     -H "Authorization: Bearer <ADMIN_JWT>"

Response: CSV file containing the kit matrix.

⬆️ Upload Kit Matrix
curl -X POST https://api.personalizedpool.com/kit/matrix \
     -H "Authorization: Bearer <ADMIN_JWT>" \
     -H "Content-Type: text/plain" \
     --data-binary @matrix.csv

🕵️ 6 · Submit Anonymous Quiz

You can submit a quiz without a user account by authenticating with a short-lived admin key.

How it works

  1. Get an IV Request a fresh IV (valid for 10 seconds) and pass it back in the x-admin-iv header.

    curl -X GET https://api.personalizedpool.com/iv

    Response (example):

    { "iv": "16-byte-iv-string" }
  2. Create the admin key (client side)

  1. Submit answers Use the same POST /answers/{quizId} endpoint. When x-admin-iv and x-admin-key are present and valid, the submission is treated as anonymous and accepted without a user JWT.

⏱️ Important: The IV from /iv expires after 10 seconds. Generate and send the key immediately before the request.


JS snapshot — generate x-admin-key

// Node.js (built-in 'crypto')
const crypto = require("crypto");

/**
 * Create x-admin-key by encrypting the current Unix timestamp in ms
 * with AES/CBC/PKCS5Padding using the provided secret and IV.
 *
 * @param {string} ivUtf8 - IV received from GET /iv (must be 16 bytes when UTF-8 encoded)
 * @param {string|Buffer} secret - Shared secret (16 or 32 bytes)
 * @returns {{ adminKey: string, timestamp: string }}
 */
function makeAdminKey(ivUtf8, secret) {
  const timestamp = String(Date.now()); // Unix ms as string
  const iv = Buffer.from(ivUtf8, "utf8");

  // Allow 16-byte (AES-128) or 32-byte (AES-256) secrets
  const key = Buffer.isBuffer(secret) ? secret : Buffer.from(secret, "utf8");
  const algo = key.length === 16 ? "aes-128-cbc"
             : key.length === 32 ? "aes-256-cbc"
             : (() => { throw new Error("Secret must be 16 or 32 bytes"); })();

  const cipher = crypto.createCipheriv(algo, key, iv);
  cipher.setAutoPadding(true);

  const encrypted = Buffer.concat([
    cipher.update(Buffer.from(timestamp, "utf8")),
    cipher.final(),
  ]);

  return {
    adminKey: encrypted.toString("base64"),
    timestamp, // optional: useful for logging/debug
  };
}

// Example usage:
// 1) Fetch iv from GET /iv
// 2) Call makeAdminKey(iv, SECRET)
// 3) Send { x-admin-iv: iv, x-admin-key: adminKey } as headers

Example: submit anonymous answers

# 1) Get IV (valid for 10s)
IV=$(curl -s https://api.personalizedpool.com/iv | jq -r .iv)

# 2) Generate x-admin-key with your app using IV + SECRET (done in JS above)
# Suppose your app prints just the base64 key:
ADMIN_KEY=$(node genAdminKey.js "$IV")  # your script wraps makeAdminKey

# 3) Submit answers anonymously
curl -X POST https://api.personalizedpool.com/answers/{quizId} \
     -H "x-admin-iv: $IV" \
     -H "x-admin-key: $ADMIN_KEY" \
     -H "Content-Type: application/json" \
     --data-binary @answers.json

Notes

✅ Final Notes