Skip to content

OBV API Reference

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

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": [...] }
}

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 check endpoint. Used by Docker healthcheck and load balancers.

Auth required: No

Response:

{ "status": "ok", "timestamp": "..." }

List all villas with basic info.

Auth required: No (protected at proxy level in production)

Response: Array of villa objects.


Create a new villa record. Typically called at the start of onboarding.

Auth required: No

Request body:

FieldTypeRequiredDescription
villaCodestringYesUnique code, 1–50 chars
villaNamestringYesDisplay name, 1–255 chars
addressstringYesStreet address, 1–500 chars
citystringYes1–100 chars
countrystringYes1–100 chars
bedroomsnumberYesInteger 1–50
bathroomsnumberYesInteger 1–50
maxGuestsnumberYesInteger 1–100
propertyTypestringYesVILLA | APARTMENT | HOUSE | CONDO | RESORT
descriptionstringNo

Response (201):

{
"success": true,
"data": { "id": "uuid", "villaCode": "...", "status": "DRAFT", ... },
"message": "Villa created successfully"
}

Errors:

  • 400 — Villa code already exists or validation failure
  • 500 — Database error

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 a villa and all cascade-deleted relations.

Auth required: No

Response:

{ "success": true, "message": "Villa deleted successfully" }

Initialize onboarding for a new villa. Creates Villa + OnboardingProgress records.

Request body:

FieldTypeRequired
villaCodestringYes
villaNamestringYes
addressstringYes
citystringYes
countrystringYes
bedroomsnumberYes
bathroomsnumberYes
maxGuestsnumberYes
propertyTypestringYes

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

Autosave partial step data. Called on every field change (debounced). Lenient — failures return { success: false } without throwing.

Request body:

FieldTypeDescription
stepnumberStep number 1–10
dataobjectPartial step data (any fields)

Step-specific save behaviour:

  • Step 1: Maps fields through fieldMappingService, updates Villa table
  • Step 2: Upserts Owner table
  • Step 3: Upserts ContractualDetails table
  • Step 4: Upserts BankDetails table
  • Step 5: Deletes and recreates all OTACredentials for the villa
  • Steps 6–10: Not handled by autosave endpoint

Response:

{
"success": true,
"villaId": "...",
"step": 1,
"savedFields": ["villaName", "city"],
"timestamp": "..."
}

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

Save Step 2: Owner Details. Upserts the Owner record.

Request body: Owner fields (firstName, lastName, email, phone, nationality, ownerType, etc.)


Save Step 3: Contractual Details. Upserts ContractualDetails.

Request body: Contract fields (contractType, commissionRate, contractStartDate, etc.)


Save Step 4: Bank Details. Upserts BankDetails.

Request body: Bank fields (accountHolderName, bankName, accountNumber, iban, etc.)


Save Step 5: OTA Credentials. Replaces all OTA credentials for the villa.

Request body:

{
"credentials": [
{
"platform": "AIRBNB",
"propertyId": "...",
"username": "...",
"password": "...",
"listingUrl": "..."
}
]
}

Save Step 6: Documents. Marks documentsUploaded = true.

Note: Actual document file uploads use POST /api/documents/upload. This endpoint only updates the progress flag.


Save Step 7: Staff Configuration. Marks staffConfigCompleted = true.


Save Step 8: Facilities Checklist. Marks facilitiesCompleted = true.


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 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 },
...
]
}

Complete the onboarding process. Requires 100% completion percentage.

On success: sets Villa.status = APPROVED, Villa.isActive = true, OnboardingProgress.status = COMPLETED.

Request body:

FieldTypeRequiredDescription
agreedToTermsbooleanYesMust be true
dataAccuracyConfirmedbooleanYesMust be true
reviewNotesstringNoOptional notes
submissionNotesstringNoOptional notes

Response:

{
"success": true,
"villaId": "...",
"status": "COMPLETED",
"progress": { ... },
"completedAt": "...",
"message": "Onboarding completed successfully! Villa is now active."
}

Errors:

  • 400completionPercentage < 100 (returns missingSteps array)
  • 400 — Validation error (terms not agreed)
  • 404 — Villa not found

These endpoints exist alongside the main wizard flow and handle specific onboarding segments independently.

MethodPathDescription
POST/api/onboarding/basic-info/startStart basic info segment
POST/api/onboarding/basic-info/saveSave basic info
POST/api/onboarding/basic-info/completeComplete basic info
POST/api/onboarding/documents/uploadUpload document (segment API)
POST/api/onboarding/documents/completeComplete documents segment
POST/api/onboarding/facilities/saveSave facilities
POST/api/onboarding/facilities/completeComplete facilities
POST/api/onboarding/photos/uploadUpload photo (segment API)
POST/api/onboarding/photos/completeComplete photos segment
POST/api/onboarding/staff/saveSave staff
POST/api/onboarding/staff/completeComplete staff
POST/api/onboarding/finalizeFinalize onboarding
GET/api/onboarding/progress/:villaIdAlternative progress endpoint

Note: The detailed request/response shapes of these legacy endpoints were not fully explored. Use the main /:villaId/ endpoints for new integrations.


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:

FieldTypeRequiredDescription
villaIdstringYesTarget villa ID
documentTypestringYesSee DocumentType enum
fileNamestringYesFile name including extension
fileContentstringYesBase64-encoded file content
fileSizenumberNoFile size in bytes
mimeTypestringNoDefault "application/octet-stream"

Behaviour:

  1. Checks if villa’s SharePoint folder exists (sharePointBasePath)
  2. If not, creates the full folder structure (lazy init with mutex lock)
  3. Uploads file to the appropriate SharePoint subfolder based on documentType
  4. Saves document metadata to PostgreSQL

SharePoint folder mapping:

DocumentTypeSharePoint Folder
PROPERTY_CONTRACTDocuments/01-Legal-Documents/Property-Contracts
INSURANCE_CERTIFICATEDocuments/02-Financial-Documents/Insurance-Policies
PROPERTY_TITLEDocuments/01-Legal-Documents/Property-Title-Deeds
TAX_DOCUMENTSDocuments/02-Financial-Documents/Tax-Documents
UTILITY_BILLSDocuments/02-Financial-Documents/Utility-Accounts
LICENSES_PERMITSDocuments/01-Legal-Documents/Licenses-Permits
INVENTORY_LISTDocuments/03-Operational-Documents/Inventory-Lists
EMERGENCY_CONTACTSDocuments/03-Operational-Documents/Emergency-Contacts
HOUSE_RULESDocuments/03-Operational-Documents/House-Rules
STAFF_CONTRACTSDocuments/04-Contracts-Agreements/Staff-Contracts
MAINTENANCE_RECORDSDocuments/04-Contracts-Agreements/Maintenance-Contracts
OTHERDocuments/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 fields
  • 404 — Villa not found
  • 500 — SharePoint upload failed or folder creation failed

Upload a photo to SharePoint with support for bedroom subfolders.

Auth required: No

Request body:

FieldTypeRequiredDescription
villaIdstringYes
categorystringYesPhoto category (lowercase, see below)
fileNamestringYes
fileContentstringYesBase64-encoded image
fileSizenumberNo
mimeTypestringNoDefault "image/jpeg"
subfolderstringNoBedroom name (required for category=bedrooms)
widthnumberNoPixel width
heightnumberNoPixel height
captionstringNo
altTextstringNo
isMainbooleanNoSet 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
}
}

Serve a photo by ID directly from the database (fileContent bytes). Used when storageLocation = "database".

Response: Binary image data with appropriate Content-Type header.


Serve a photo thumbnail by ID from the database (thumbnailContent bytes).

Response: Binary image data.


Note: Purpose not fully determined from codebase — requires manual verification.


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

Serve a facility item photo by checklist item ID. Returns the photoData bytes stored in the database.

Response: Binary image data.


Get the owner record for a villa.

Response: Owner object or 404.


Create a new owner record.

Request body: Owner fields (villaId required).


All dashboard endpoints return aggregated data for the management UI. None require authentication tokens in the current implementation.

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 },
...
]
}
}

List of villas with status summary for the dashboard table.


List of all owners with associated villa names.


List of all staff across all villas.


List of all documents with villa association and expiry status.


Management overview combining villas, owners, and key metrics.


Breakdown of onboarding progress across all villas.


These endpoints are intended for internal use and should not be exposed publicly in production.

Lists all registered API routes. Useful for introspection during development.

Returns the in-memory API monitoring data (last 100 requests, call statistics, slow requests).

Internal documentation dashboard.

Checks database for data integrity. Development only.

Test endpoint. Development only.


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-ID header 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.


Status CodeMeaning
200Success
201Resource created
204No content (CORS preflight)
400Validation error or bad request
404Resource not found
500Internal server error

Validation errors include a structured data.errors array with field, message, and code for each failed rule (Zod-powered).