All endpoints are JSON unless noted. Base URL: /.
A successful POST /api/auth/login sets the rent-tool-session cookie (HTTP-only, JWT).
The middleware in src/middleware.ts rejects any request without a valid session
cookie with 401 { "error": "Unauthorized" }, except for these public paths:
/api/auth/login/api/calendar/feed/*/api/calendar/cron
In the table below, Auth = session means a valid session cookie is required;
public means no auth needed; cron means it expects ?token=$CRON_SECRET.
Errors follow { "error": string }. Standard codes:
400— invalid input (missing field, malformed body, bad ID)401— missing or invalid session404— record not found409— conflict (e.g. overlapping reservation)500— unhandled server error
Body: { "username": string, "password": string }
On success: sets rent-tool-session cookie; returns { user: { userId, username, role } }.
On failure: 401 { "error": "Invalid credentials" }.
Clears the session cookie. Returns { success: true }.
Returns the current user: { userId, username, role } or 401.
curl -X POST -H "Content-Type: application/json" \
-d '{"username":"admin","password":"secret123"}' \
https://app.example.com/api/auth/loginReturns Property[] with nested reservations, calendarLinks, and counts.
Body: { "name": string, "minNights"?: number, "checkInTime"?: "HH:MM", "checkOutTime"?: "HH:MM", "bookingWindow"?: number }.
Returns the created Property.
Body: any subset of the POST fields. Returns the updated Property or 400 for non-numeric id.
Cascade-deletes the property and all related reservations/guests/links/events.
Returns { success: true }.
Returns Reservation[] with _count.guests. propertyId query param is required.
Body: { "name": string, "checkIn": ISODate, "checkOut": ISODate, "platform"?: "airbnb"|"booking"|"direct", "propertyId": number, "linkedEventUid"?: string }.
Validates that checkIn/checkOut are valid dates and checkOut > checkIn.
Returns 409 if dates overlap an existing reservation for the same property.
Body: any of { name?, checkIn?, checkOut?, platform? }.
Cascade-deletes the reservation and its guests.
Returns Guest[] for the reservation, ordered by id.
Body: any subset of guest fields plus parentId (for child→adult linkage).
passportNumber is stripped of whitespace; issuedBy is sanitized to Latin
letters/digits/spaces. Returns the updated Guest.
Returns { success: true }.
Returns User[] (without password hashes).
Body: { "username": string, "password": string (>= 8 chars), "role"?: "user"|"superadmin" }.
Returns { [key]: string } of all AppSettings rows.
Body: { [key]: string }. Upserts each key/value.
multipart/form-data body:
files— one or more JPG/PNG/PDF passport imagesreservationId— string (numeric)
Calls Gemini Vision, sanitizes results, and creates Guest rows linked to the reservation.
Returns { data: SavedItem[] } where each item is either a created guest, a _action: "visa_updated" guest, or _action: "visa_no_match".
curl -b cookies.txt \
-F "files=@passport.jpg" \
-F "reservationId=42" \
https://app.example.com/api/extractReturns CalendarLink[] for the property.
Body: { propertyId, platform: "airbnb"|"booking", icalExportUrl, bufferBefore?, bufferAfter? }.
Update buffers or URL.
Triggers a sync of all calendar links across all properties. Returns
{ propertiesSynced, eventsCreated, eventsUpdated, errors }.
Returns last-sync metadata: { lastFetchedAt, lastError, eventCounts: { airbnb, booking } }.
Body: { icalExportUrl }. Fetches and validates the URL without saving.
Returns { ok, eventCount, error? }.
Public iCal feed (text/calendar) of buffered events for the property,
intended to be imported by Airbnb / Booking.com. Optional ?platform= filter.
Same content as above; the trailing filename is for platform compatibility
(some require a .ics suffix in the URL).
Triggers the same sync as POST /api/calendar/sync but is meant to be hit
by an external scheduler (cron-job.org). Rejects requests with the wrong token.
Reads / updates the cron schedule settings.
Returns per-property feed status: { properties: [{ id, name, airbnbFeed: { url, status, eventCount }, bookingFeed: {...} }] }.
Body: { propertyId, date: "YYYY-MM-DD", type: "open"|"closed", note?: string }.