Skip to content

Backend API

Purpose

This page is the human-written API guide for Poolia.BACK. It complements the autogenerated OpenAPI artifacts:

  • backend/generated/openapi.md for readable endpoint-level reference
  • backend/generated/openapi.json for raw machine-readable schema

Use this page to understand the API surface by domain, the authentication model, and the most important behaviors that are not obvious from an endpoint list alone.

Base characteristics

  • Framework: FastAPI
  • Format: JSON for standard API traffic, multipart form-data for file uploads
  • Health endpoint: GET /health
  • Authentication style: bearer JWT via OAuth2 password bearer flow

Authentication model

Register

POST /auth/register

Creates a user account and returns a bearer token.

Expected payload shape:

  • name
  • lastName
  • email
  • password
  • role

Behavior:

  • rejects duplicate emails
  • hashes the password before persistence
  • sets user status to active
  • returns access_token + token_type=bearer

Login

POST /auth/login

Validates email and password and returns a bearer token.

Behavior:

  • rejects unknown users
  • rejects deleted users
  • rejects users that are not active
  • rejects accounts without a password hash
  • updates last_login_at

Logout

POST /auth/logout

Currently returns a success message only. It does not revoke tokens or invalidate server-side session state.

Security scope in the current implementation

Authentication is not applied uniformly across all routers.

Protected routes currently include:

  • GET /users/me
  • POST /users/me/change-password
  • all messaging endpoints

Most CRUD endpoints for users, pools, visits, invites, pool values, and photos are currently router-accessible without an auth dependency. That should be treated as the current implementation state and not as a recommended final policy.

Domain-by-domain API guide

1. Health

GET /health

Returns a simple liveness response:

{"status": "ok"}

Useful for deployment verification and container health checks.

2. Authentication

POST /auth/register

Creates a user and returns a token.

POST /auth/login

Authenticates a user and returns a token.

POST /auth/logout

Placeholder logout endpoint.

3. Users

Prefix: /users

GET /users

Returns all users.

POST /users

Creates a user directly from a UserCreate payload.

This is separate from /auth/register and does not return a token.

GET /users/me

Returns the authenticated user.

POST /users/me/change-password

Allows the authenticated user to change their password.

Validation rules:

  • current password must match
  • new password must differ from current password

GET /users/{user_id}

Returns one user by ID.

PATCH /users/{user_id}

Applies partial updates to a user.

DELETE /users/{user_id}

Performs a soft delete and returns a small confirmation payload.

4. Pools

Prefix: /pools

Pools are the core business resource.

POST /pools

Creates a new pool.

Important validation:

  • ownerId must reference a user whose role is owner

GET /pools

Lists pools.

Supported query parameter:

  • ownerId (optional)

GET /pools/{pool_id}

Returns one pool by ID.

PATCH /pools/{pool_id}

Partially updates a pool.

If owner_id is changed, the new owner is validated.

DELETE /pools/{pool_id}

Deletes a pool.

DELETE /pools/{pool_id}/technicians/{technician_id}

Removes an assigned technician from the pool.

Validation:

  • target user must be a technician
  • technician must actually be assigned to the pool

GET /pools/user/{user_id}

Lists pools owned by a specific owner.

GET /pools/technician/{tech_id}

Lists pools assigned to a technician.

The service validates that the target user has technician role.

5. Pool dashboard

GET /pools/{poolId}/dashboard

Returns a dashboard-oriented aggregate view.

This endpoint composes data from:

  • pool
  • owner
  • latest pool values
  • latest visit
  • latest visit technician

Returned information includes items such as:

  • owner email
  • technician email
  • pool metadata
  • latest readings (pH, chlorine, temperature)
  • last visit date
  • last visit status / pool health state
  • observations

This endpoint is one of the best examples of a service-layer aggregation endpoint.

6. Alert configuration

GET /pools/{pool_id}/alert-config

Returns the alert configuration for a pool.

If no alert configuration exists, the service creates a default configuration on demand with threshold defaults for:

  • pH
  • chlorine
  • temperature

PUT /pools/{pool_id}/alert-config

Creates or updates the alert configuration for the pool.

This is effectively an upsert endpoint.

7. Pool values

Prefix: /pool-values

POST /pool-values

Creates a pool-values document.

GET /pool-values

Lists all pool-values documents.

GET /pool-values/{pool_values_id}

Returns a single pool-values document.

PATCH /pool-values/{pool_values_id}

Partially updates a pool-values document.

DELETE /pool-values/{pool_values_id}

Deletes a pool-values document.

8. Visits

Prefix: /visits

POST /visits

Creates a visit directly from a visit payload.

GET /visits

Lists all visits.

GET /visits/{visit_id}

Returns a single visit.

PATCH /visits/{visit_id}

Partially updates a visit.

DELETE /visits/{visit_id}

Deletes a visit.

9. Pool-scoped visit creation workflow

POST /pools/{poolId}/visits

This is a richer workflow than the generic /visits create endpoint.

It creates both:

  • a PoolValues snapshot
  • a linked Visit

Validation rules:

  • pool must exist
  • technician must exist
  • technician must have role technician
  • technician must already be assigned to the pool

The response returns both created entities.

GET /pools/{poolId}/visits

Returns visits for a specific pool.

10. Invites

Prefix: /invites

POST /invites

Creates an invite directly.

GET /invites

Lists all invites.

GET /invites/{invite_id}

Returns one invite by ID.

PATCH /invites/{invite_id}

Updates an invite.

DELETE /invites/{invite_id}

Deletes an invite.

GET /invites/technicians/{technician_id}

Lists invites for a technician.

Validation:

  • target user must exist
  • target user must have technician role

GET /invites/technicians/{technician_id}/pending

Lists only pending invites for a technician.

POST /invites/{inviteId}/accept

Accepts an invite.

Behavior:

  • invite must be pending
  • pool must exist
  • technician must not already be assigned
  • technician is appended to pool.technician_ids
  • invite status becomes accepted
  • respondedAt is set

POST /invites/{inviteId}/decline

Declines an invite.

Behavior:

  • invite must be pending
  • invite status becomes declined
  • respondedAt is set

11. Pool-to-technician invitation by email

POST /pools/{pool_id}/invites

Creates an invite for a technician identified by email.

Query parameter:

  • ownerId (required)

Body:

  • technicianEmail

Validation rules:

  • pool must exist
  • owner must match the pool owner
  • technician must exist
  • technician must have technician role
  • technician must not already be assigned
  • there must not already be a pending invite for that pool-technician pair

12. Photos

Prefix: /photos

The photo subsystem stores metadata in MongoDB and the file in Azure Blob Storage.

POST /photos/users/{user_id}/profile

Uploads a user profile photo.

Behavior:

  • deletes existing profile photo for the user if one exists
  • uploads new binary to Azure Blob Storage
  • persists metadata in MongoDB

GET /photos/users/{user_id}/profile

Returns profile-photo metadata plus a generated read URL.

POST /photos/visits/{visit_id}

Uploads a visit photo.

GET /photos/visits/{visit_id}

Lists all photos associated with a visit.

POST /photos/pools/{pool_id}

Uploads a pool photo.

Behavior:

  • if an existing pool photo exists, it is replaced

GET /photos/pools/{pool_id}

Returns the current pool photo.

DELETE /photos/{photo_id}

Deletes the photo metadata and attempts to delete the corresponding blob.

13. Messaging

Messaging routes are mounted without a common prefix, so paths are rooted directly.

This is currently the most authorization-aware subsystem in the project.

Threads

POST /pools/{pool_id}/threads

Creates a thread for a pool.

Rules:

  • user must be authenticated
  • user must have access to the pool (admin, pool owner, or assigned technician)
  • the initial thread also creates the initial message
  • if the initial message type is answer, the thread is marked answered

GET /pools/{pool_id}/threads

Lists threads for a pool.

Optional query parameter:

  • status

Allowed values:

  • open
  • answered
  • closed

GET /threads/{thread_id}

Returns a thread.

PATCH /threads/{thread_id}

Updates a thread.

Messages

GET /threads/{thread_id}/messages

Lists messages for a thread, including attached photo metadata.

GET /messages/{message_id}

Returns one message with its photos.

POST /threads/{thread_id}/messages

Creates a new message in a thread.

Rules:

  • thread must not be closed
  • replyToMessageId, if present, must belong to the same thread
  • answering a thread changes status to answered
  • posting a new question on an answered thread reopens it to open
  • thread cache fields are updated

PATCH /messages/{message_id}

Updates a message body.

Rules:

  • only the author or an admin can edit the message
  • if the edited message is the cached latest message, thread cache is refreshed

Message photos

POST /threads/{thread_id}/messages/{message_id}/photos

Uploads a photo attached to a specific message and returns the refreshed message response.

Rules:

  • user must have pool access
  • message must belong to the thread
  • the actual file upload is delegated to the photo service

Response modeling

The API mixes direct Beanie document responses with dedicated schema responses.

Examples:

  • direct document responses: users, pools, visits, invites, pool values
  • dedicated response schemas: auth token responses, pool dashboard, photo responses, messaging responses

This is practical and common for internal APIs, but it also means the public response contract is partly coupled to the persistence model.

Error behavior

Errors are raised through HTTPException throughout the service layer.

Common patterns:

  • 400 Bad Request for invalid role/relationship state or malformed business transitions
  • 401 Unauthorized for auth/token failures
  • 403 Forbidden for access violations or inactive/deleted login attempts
  • 404 Not Found for missing entities
  • 409 Conflict for duplicate assignment / duplicate pending invite scenarios

The user-facing error messages are currently a mix of Spanish and English.

API design observations

Strengths

  • Clear domain separation by router
  • Good use of typed payloads and response models
  • The pool dashboard and messaging flows show meaningful service-layer orchestration
  • The messaging subsystem has better access control than the rest of the API

Current inconsistencies

  • not all routes are authenticated
  • some path parameter names differ stylistically (inviteId vs invite_id)
  • some routes return raw models, others dedicated response schemas
  • logout is not stateful

For readers of the documentation site:

  • start with this page for intent and domain grouping
  • use backend/generated/openapi.md for endpoint-level detail
  • use backend/generated/openapi.json for tooling, validation, or client generation