Backend API
Purpose
This page is the human-written API guide for Poolia.BACK. It complements the autogenerated OpenAPI artifacts:
backend/generated/openapi.mdfor readable endpoint-level referencebackend/generated/openapi.jsonfor 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
- 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/mePOST /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:
ownerIdmust reference a user whose role isowner
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
PoolValuessnapshot - 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 respondedAtis set
POST /invites/{inviteId}/decline
Declines an invite.
Behavior:
- invite must be
pending - invite status becomes
declined respondedAtis 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 markedanswered
GET /pools/{pool_id}/threads
Lists threads for a pool.
Optional query parameter:
status
Allowed values:
openansweredclosed
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 Requestfor invalid role/relationship state or malformed business transitions401 Unauthorizedfor auth/token failures403 Forbiddenfor access violations or inactive/deleted login attempts404 Not Foundfor missing entities409 Conflictfor 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 (
inviteIdvsinvite_id) - some routes return raw models, others dedicated response schemas
- logout is not stateful
Recommended usage inside docs
For readers of the documentation site:
- start with this page for intent and domain grouping
- use
backend/generated/openapi.mdfor endpoint-level detail - use
backend/generated/openapi.jsonfor tooling, validation, or client generation