OBV API Reference
Overview
Section titled “Overview”The OBV backend is a NitroJS (Nitropack) server exposing a REST API. Routes follow the file-based convention of NitroJS where file names map directly to URL paths and HTTP methods.
Base URL (production): https://obv.inspiringlivingsolutions.com/api
Base URL (local): http://localhost:4002/api
Response Envelope
Section titled “Response Envelope”Most endpoints return a consistent JSON shape:
{ "success": true, "data": { ... }, "message": "Human-readable message", "timestamp": "2024-09-16T10:00:00.000Z"}Error responses:
{ "statusCode": 400, "statusMessage": "Validation failed", "data": { "errors": [...] }}Authentication
Section titled “Authentication”The frontend sends Clerk JWTs as Authorization: Bearer <token>. The backend verifies tokens using the Clerk JWKS endpoint (CLERK_JWKS_URL).
Note: Route-level auth middleware is not enforced on every endpoint in the current codebase. Auth is primarily enforced at the Next.js middleware layer. In production, the Caddy reverse proxy restricts direct backend access.
All /api/** routes accept CORS from configured origins. In development, all origins are allowed (*). Preflight OPTIONS requests are handled by the cors.ts middleware and return HTTP 204.
Allowed headers: Content-Type, Authorization, X-Requested-With, Accept, Origin, If-Match, X-Auto-Save
Health
Section titled “Health”GET /api/health
Section titled “GET /api/health”Health check endpoint. Used by Docker healthcheck and load balancers.
Auth required: No
Response:
{ "status": "ok", "timestamp": "..." }Villas
Section titled “Villas”GET /api/villas
Section titled “GET /api/villas”List all villas with basic info.
Auth required: No (protected at proxy level in production)
Response: Array of villa objects.
POST /api/villas
Section titled “POST /api/villas”Create a new villa record. Typically called at the start of onboarding.
Auth required: No
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
villaCode | string | Yes | Unique code, 1–50 chars |
villaName | string | Yes | Display name, 1–255 chars |
address | string | Yes | Street address, 1–500 chars |
city | string | Yes | 1–100 chars |
country | string | Yes | 1–100 chars |
bedrooms | number | Yes | Integer 1–50 |
bathrooms | number | Yes | Integer 1–50 |
maxGuests | number | Yes | Integer 1–100 |
propertyType | string | Yes | VILLA | APARTMENT | HOUSE | CONDO | RESORT |
description | string | No | — |
Response (201):
{ "success": true, "data": { "id": "uuid", "villaCode": "...", "status": "DRAFT", ... }, "message": "Villa created successfully"}Errors:
400— Villa code already exists or validation failure500— Database error
GET /api/villas/:id
Section titled “GET /api/villas/:id”Get a single villa with all related data.
Auth required: No
Response:
{ "success": true, "data": { "villa": { ... }, "ownerDetails": { ... } | null, "contractualDetails": { ... } | null, "bankDetails": { ... } | null, "otaCredentials": [ ... ], "documents": [ ... ], "staff": [ ... ], "facilities": [ ... ], "photos": [ ... ], "onboarding": { ... } | null }}Errors:
404— Villa not found
DELETE /api/villas/:id
Section titled “DELETE /api/villas/:id”Delete a villa and all cascade-deleted relations.
Auth required: No
Response:
{ "success": true, "message": "Villa deleted successfully" }Onboarding — Main Flow
Section titled “Onboarding — Main Flow”POST /api/onboarding/start
Section titled “POST /api/onboarding/start”Initialize onboarding for a new villa. Creates Villa + OnboardingProgress records.
Request body:
| Field | Type | Required |
|---|---|---|
villaCode | string | Yes |
villaName | string | Yes |
address | string | Yes |
city | string | Yes |
country | string | Yes |
bedrooms | number | Yes |
bathrooms | number | Yes |
maxGuests | number | Yes |
propertyType | string | Yes |
Response:
{ "villaId": "uuid", "progress": { "villaId": "...", "completionPercentage": 0, "status": "IN_PROGRESS", "currentStep": 1, "totalSteps": 10, "stepsCompleted": [false, false, false, false, false, false, false, false, false, false], "updatedAt": "..." }}GET /api/onboarding/:villaId/progress
Section titled “GET /api/onboarding/:villaId/progress”Get current onboarding progress for a villa.
Response:
{ "success": true, "progress": { "villaId": "...", "completionPercentage": 40, "status": "IN_PROGRESS", "currentStep": 5, "totalSteps": 10, "stepsCompleted": [true, true, true, true, false, false, false, false, false, false], "updatedAt": "..." }, "timestamp": "..."}Errors:
404— Villa not found
POST /api/onboarding/:villaId/autosave
Section titled “POST /api/onboarding/:villaId/autosave”Autosave partial step data. Called on every field change (debounced). Lenient — failures return { success: false } without throwing.
Request body:
| Field | Type | Description |
|---|---|---|
step | number | Step number 1–10 |
data | object | Partial step data (any fields) |
Step-specific save behaviour:
- Step 1: Maps fields through
fieldMappingService, updatesVillatable - Step 2: Upserts
Ownertable - Step 3: Upserts
ContractualDetailstable - Step 4: Upserts
BankDetailstable - Step 5: Deletes and recreates all
OTACredentialsfor the villa - Steps 6–10: Not handled by autosave endpoint
Response:
{ "success": true, "villaId": "...", "step": 1, "savedFields": ["villaName", "city"], "timestamp": "..."}POST /api/onboarding/:villaId/save-step-1
Section titled “POST /api/onboarding/:villaId/save-step-1”Save Step 1: Villa Information. Marks villaInfoCompleted = true in OnboardingProgress.
Request body: Villa information fields (see Villa model — all optional for partial saves)
Response:
{ "success": true, "villaId": "...", "step": 1, "savedFields": ["villaName", "address", "bedrooms", ...], "timestamp": "..."}POST /api/onboarding/:villaId/save-step-2
Section titled “POST /api/onboarding/:villaId/save-step-2”Save Step 2: Owner Details. Upserts the Owner record.
Request body: Owner fields (firstName, lastName, email, phone, nationality, ownerType, etc.)
POST /api/onboarding/:villaId/save-step-3
Section titled “POST /api/onboarding/:villaId/save-step-3”Save Step 3: Contractual Details. Upserts ContractualDetails.
Request body: Contract fields (contractType, commissionRate, contractStartDate, etc.)
POST /api/onboarding/:villaId/save-step-4
Section titled “POST /api/onboarding/:villaId/save-step-4”Save Step 4: Bank Details. Upserts BankDetails.
Request body: Bank fields (accountHolderName, bankName, accountNumber, iban, etc.)
POST /api/onboarding/:villaId/save-step-5
Section titled “POST /api/onboarding/:villaId/save-step-5”Save Step 5: OTA Credentials. Replaces all OTA credentials for the villa.
Request body:
{ "credentials": [ { "platform": "AIRBNB", "propertyId": "...", "username": "...", "password": "...", "listingUrl": "..." } ]}POST /api/onboarding/:villaId/save-step-6
Section titled “POST /api/onboarding/:villaId/save-step-6”Save Step 6: Documents. Marks documentsUploaded = true.
Note: Actual document file uploads use
POST /api/documents/upload. This endpoint only updates the progress flag.
POST /api/onboarding/:villaId/save-step-7
Section titled “POST /api/onboarding/:villaId/save-step-7”Save Step 7: Staff Configuration. Marks staffConfigCompleted = true.
POST /api/onboarding/:villaId/save-step-8
Section titled “POST /api/onboarding/:villaId/save-step-8”Save Step 8: Facilities Checklist. Marks facilitiesCompleted = true.
POST /api/onboarding/:villaId/save-step-9
Section titled “POST /api/onboarding/:villaId/save-step-9”Save Step 9: Photo Upload. Marks photosUploaded = true.
Note: Actual photo uploads use
POST /api/photos/upload.
POST /api/onboarding/:villaId/save-step-10
Section titled “POST /api/onboarding/:villaId/save-step-10”Save Step 10: Review. Marks reviewCompleted = true.
POST /api/onboarding/:villaId/save-steps-1-5
Section titled “POST /api/onboarding/:villaId/save-steps-1-5”Batch save for steps 1–5 in a single request. Used for bulk data imports.
POST /api/onboarding/:villaId/step/:stepNumber
Section titled “POST /api/onboarding/:villaId/step/:stepNumber”Generic step save endpoint. Accepts any step number and data.
Request body:
{ "data": { ... }, "completed": true}GET /api/onboarding/:villaId/summary
Section titled “GET /api/onboarding/:villaId/summary”Get a full onboarding summary including step-level data existence.
Response:
{ "progress": { ... }, "stepDetails": [ { "step": 1, "name": "villaInfo", "completed": true, "hasData": true }, { "step": 2, "name": "ownerDetails", "completed": false, "hasData": false }, ... ]}POST /api/onboarding/:villaId/complete
Section titled “POST /api/onboarding/:villaId/complete”Complete the onboarding process. Requires 100% completion percentage.
On success: sets Villa.status = APPROVED, Villa.isActive = true, OnboardingProgress.status = COMPLETED.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
agreedToTerms | boolean | Yes | Must be true |
dataAccuracyConfirmed | boolean | Yes | Must be true |
reviewNotes | string | No | Optional notes |
submissionNotes | string | No | Optional notes |
Response:
{ "success": true, "villaId": "...", "status": "COMPLETED", "progress": { ... }, "completedAt": "...", "message": "Onboarding completed successfully! Villa is now active."}Errors:
400—completionPercentage < 100(returnsmissingStepsarray)400— Validation error (terms not agreed)404— Villa not found
Onboarding — Legacy Segment Endpoints
Section titled “Onboarding — Legacy Segment Endpoints”These endpoints exist alongside the main wizard flow and handle specific onboarding segments independently.
| Method | Path | Description |
|---|---|---|
POST | /api/onboarding/basic-info/start | Start basic info segment |
POST | /api/onboarding/basic-info/save | Save basic info |
POST | /api/onboarding/basic-info/complete | Complete basic info |
POST | /api/onboarding/documents/upload | Upload document (segment API) |
POST | /api/onboarding/documents/complete | Complete documents segment |
POST | /api/onboarding/facilities/save | Save facilities |
POST | /api/onboarding/facilities/complete | Complete facilities |
POST | /api/onboarding/photos/upload | Upload photo (segment API) |
POST | /api/onboarding/photos/complete | Complete photos segment |
POST | /api/onboarding/staff/save | Save staff |
POST | /api/onboarding/staff/complete | Complete staff |
POST | /api/onboarding/finalize | Finalize onboarding |
GET | /api/onboarding/progress/:villaId | Alternative progress endpoint |
Note: The detailed request/response shapes of these legacy endpoints were not fully explored. Use the main
/:villaId/endpoints for new integrations.
Documents
Section titled “Documents”POST /api/documents/upload
Section titled “POST /api/documents/upload”Upload a document to SharePoint and save metadata to the database.
Files are sent as base64-encoded strings in the request body (not multipart form data).
Auth required: No
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
villaId | string | Yes | Target villa ID |
documentType | string | Yes | See DocumentType enum |
fileName | string | Yes | File name including extension |
fileContent | string | Yes | Base64-encoded file content |
fileSize | number | No | File size in bytes |
mimeType | string | No | Default "application/octet-stream" |
Behaviour:
- Checks if villa’s SharePoint folder exists (
sharePointBasePath) - If not, creates the full folder structure (lazy init with mutex lock)
- Uploads file to the appropriate SharePoint subfolder based on
documentType - Saves document metadata to PostgreSQL
SharePoint folder mapping:
| DocumentType | SharePoint Folder |
|---|---|
PROPERTY_CONTRACT | Documents/01-Legal-Documents/Property-Contracts |
INSURANCE_CERTIFICATE | Documents/02-Financial-Documents/Insurance-Policies |
PROPERTY_TITLE | Documents/01-Legal-Documents/Property-Title-Deeds |
TAX_DOCUMENTS | Documents/02-Financial-Documents/Tax-Documents |
UTILITY_BILLS | Documents/02-Financial-Documents/Utility-Accounts |
LICENSES_PERMITS | Documents/01-Legal-Documents/Licenses-Permits |
INVENTORY_LIST | Documents/03-Operational-Documents/Inventory-Lists |
EMERGENCY_CONTACTS | Documents/03-Operational-Documents/Emergency-Contacts |
HOUSE_RULES | Documents/03-Operational-Documents/House-Rules |
STAFF_CONTRACTS | Documents/04-Contracts-Agreements/Staff-Contracts |
MAINTENANCE_RECORDS | Documents/04-Contracts-Agreements/Maintenance-Contracts |
OTHER | Documents/Other |
Response:
{ "success": true, "data": { "id": "uuid", "fileName": "contract.pdf", "documentType": "PROPERTY_CONTRACT", "fileUrl": "https://...", "sharePointUrl": "https://...", "sharePointFileId": "...", "sharePointPath": "Villas/VillaName_id/Documents/..." }, "message": "Document uploaded successfully"}Errors:
400— Missing required fields404— Villa not found500— SharePoint upload failed or folder creation failed
Photos
Section titled “Photos”POST /api/photos/upload
Section titled “POST /api/photos/upload”Upload a photo to SharePoint with support for bedroom subfolders.
Auth required: No
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
villaId | string | Yes | — |
category | string | Yes | Photo category (lowercase, see below) |
fileName | string | Yes | — |
fileContent | string | Yes | Base64-encoded image |
fileSize | number | No | — |
mimeType | string | No | Default "image/jpeg" |
subfolder | string | No | Bedroom name (required for category=bedrooms) |
width | number | No | Pixel width |
height | number | No | Pixel height |
caption | string | No | — |
altText | string | No | — |
isMain | boolean | No | Set as main photo |
Category values (lowercase): logo | floor_plan | exterior_views | interior_living_spaces | bedrooms | bathrooms | kitchen | dining_areas | pool_outdoor_areas | garden_landscaping | amenities_facilities | views_surroundings | staff_areas | utility_areas | videos | drone_shots | entertainment
Bedroom subfolder: When category = "bedrooms", provide subfolder with the bedroom name. The folder Photos/Bedrooms/{sanitizedName} is created automatically if it doesn’t exist.
Response:
{ "success": true, "data": { "id": "uuid", "fileName": "pool.jpg", "category": "POOL_OUTDOOR_AREAS", "subfolder": null, "fileUrl": "https://...", "sharePointUrl": "https://...", "sharePointFileId": "...", "sharePointPath": "Villas/.../Photos/Pool-Outdoor-Areas/pool.jpg", "thumbnailUrl": null, "width": 1920, "height": 1080 }}GET /api/photos-enhanced/public/:id
Section titled “GET /api/photos-enhanced/public/:id”Serve a photo by ID directly from the database (fileContent bytes). Used when storageLocation = "database".
Response: Binary image data with appropriate Content-Type header.
GET /api/photos-enhanced/thumbnail/:id
Section titled “GET /api/photos-enhanced/thumbnail/:id”Serve a photo thumbnail by ID from the database (thumbnailContent bytes).
Response: Binary image data.
POST /api/photos-enhanced
Section titled “POST /api/photos-enhanced”Note: Purpose not fully determined from codebase — requires manual verification.
Facilities
Section titled “Facilities”GET /api/facilities/villa/:villaId
Section titled “GET /api/facilities/villa/:villaId”Get all facility checklist items for a villa.
Response:
{ "success": true, "data": [ { "id": "...", "villaId": "...", "category": "kitchen_dining", "subcategory": "Appliances", "itemName": "Dishwasher", "isAvailable": true, "quantity": 1, "condition": "good", "photoUrl": "/api/facilities/photo/uuid" } ]}GET /api/facilities/photo/:id
Section titled “GET /api/facilities/photo/:id”Serve a facility item photo by checklist item ID. Returns the photoData bytes stored in the database.
Response: Binary image data.
Owners
Section titled “Owners”GET /api/owners/:villaId
Section titled “GET /api/owners/:villaId”Get the owner record for a villa.
Response: Owner object or 404.
POST /api/owners
Section titled “POST /api/owners”Create a new owner record.
Request body: Owner fields (villaId required).
Dashboard
Section titled “Dashboard”All dashboard endpoints return aggregated data for the management UI. None require authentication tokens in the current implementation.
GET /api/dashboard/stats
Section titled “GET /api/dashboard/stats”Overall platform statistics.
Response:
{ "success": true, "data": { "totalVillas": 42, "activeVillas": 30, "pendingOnboarding": 8, "onboardingInProgress": 5, "onboardingCompleted": 25, "staffCount": 156, "totalDocuments": 320, "totalOwners": 38, "averageCompletionTime": 87, "mostSkippedFields": [ { "fieldName": "vatRegistrationNumber", "skipCount": 12 }, ... ] }}GET /api/dashboard/villas
Section titled “GET /api/dashboard/villas”List of villas with status summary for the dashboard table.
GET /api/dashboard/owners
Section titled “GET /api/dashboard/owners”List of all owners with associated villa names.
GET /api/dashboard/staff
Section titled “GET /api/dashboard/staff”List of all staff across all villas.
GET /api/dashboard/documents
Section titled “GET /api/dashboard/documents”List of all documents with villa association and expiry status.
GET /api/dashboard/management
Section titled “GET /api/dashboard/management”Management overview combining villas, owners, and key metrics.
GET /api/dashboard/onboarding-overview
Section titled “GET /api/dashboard/onboarding-overview”Breakdown of onboarding progress across all villas.
Internal / Monitoring
Section titled “Internal / Monitoring”These endpoints are intended for internal use and should not be exposed publicly in production.
GET /api/docs/routes
Section titled “GET /api/docs/routes”Lists all registered API routes. Useful for introspection during development.
GET /api/docs/monitoring
Section titled “GET /api/docs/monitoring”Returns the in-memory API monitoring data (last 100 requests, call statistics, slow requests).
GET /api/docs/dashboard
Section titled “GET /api/docs/dashboard”Internal documentation dashboard.
GET /api/debug/check-data
Section titled “GET /api/debug/check-data”Checks database for data integrity. Development only.
GET /api/debug/test
Section titled “GET /api/debug/test”Test endpoint. Development only.
API Monitoring
Section titled “API Monitoring”Every request passes through apiMonitoring.ts middleware which:
- Logs method, path, status code, duration, and client IP
- Stores the last 100 API calls in memory
- Exposes statistics via
GET /api/docs/monitoring - Adds
X-Request-IDheader to every response
Note: This is an in-memory store. Data is lost on server restart. For production, a persistent store (Redis or database) should be used.
Error Reference
Section titled “Error Reference”| Status Code | Meaning |
|---|---|
200 | Success |
201 | Resource created |
204 | No content (CORS preflight) |
400 | Validation error or bad request |
404 | Resource not found |
500 | Internal server error |
Validation errors include a structured data.errors array with field, message, and code for each failed rule (Zod-powered).