API reference
Every route the API serves, who may call it, and how sign-in, permissions, errors, lists, bookings and limits work.
For the Full Stack package
Base URL and response format
Every route is served under the /api prefix. Locally the base URL is http://localhost:8000/api. On a server it is your API's address followed by /api, the same value both frontends read from NEXT_PUBLIC_API_BASE_URL (Environment variables). Photographs are served outside the prefix, at /media/….
The website's routes are public; the dashboard's need an admin's bearer token, and most also a permission. Request bodies are JSON, except file uploads. Every answer comes in the same envelope:
{
"success": true,
"data": { },
"message": ""
}data holds the result. message is a short sentence some writes fill in, in the request's language, and empty otherwise. Prices are whole US dollars, and dates are company calendar days written YYYY-MM-DD.
The health check needs no token:
curl http://localhost:8000/api/health{"success":true,"data":{"status":"ok","database":"up"},"message":""}When the database cannot be read it still answers 200, with "status":"degraded" and "database":"down". The Docker image's health check reads that field.
Errors
A failure keeps the envelope, with success: false and the HTTP status. message is a sentence in the request's language, code the untranslated key behind it, and a refusal that names something adds the values behind its sentence in details:
{
"success": false,
"data": null,
"message": "Booking B0021 already holds some of these nights.",
"code": "dates_taken",
"details": { "held_by": "booking", "reference": "B0021" }
}A body the validation pipe refuses carries code: "validation_failed" and errors, keyed by each field's full name:
{
"success": false,
"data": null,
"message": "guest_email must be an email",
"errors": { "guest_email": ["guest_email must be an email"] },
"code": "validation_failed"
}- Branch on
code, never on the sentence, which changes with the language. Booking and availability refusals carry codes such asdates_taken,min_nights,capacity,cancellation_window_passed,awaiting_paymentandpayment_method_unavailable, and a limitrate_limited. errorsappears on a validation failure, with the problems under the field's name as the request spelled it (check_in,guest_email). An unknown field in a body is refused.- A guarded route with no token, or an expired one, answers
401; a token without the permission answers403. - Nights already held answer
409, a booking step from the wrong status422, and a request over a limit429.
Lists and paging
{ "data": [ ], "page": 1, "page_size": 6, "total": 12, "total_pages": 2 }- Stays, bookings, inquiries and reviews page with
pageandpage_size: 6 stays a page on the website (at most 24), 20 in the dashboard (at most 50), 10 bookings and 20 inquiries (at most 50 each), 8 reviews. The dashboard's lists also returncountsper status. - Admins, roles and settings page with
pageandpage_count(settings also takepage_size), 10 by default and at most 100, and answer{ data, page, limit, total, totalPages }. - The website's stay filters are validated: an unknown value is refused with
400rather than ignored, while a key the API does not read is ignored, so a shared link with extra parameters still works.
Language
Send Accept-Language: ar for Arabic messages; anything else answers in English. Stays, destinations and the home page questions carry both languages as { "en": "…", "ar": "…" } whatever the header says, and the client picks one.
Signing in
Sign in as an admin
Terminalcurl -X POST http://localhost:8000/api/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"admin@hollina.example","password":"Admin@123"}'data.access_tokenis the token, withtoken_typeBearerand the admin with their roles and permissions. The token lastsJWT_EXPIRATION,7dby default, andexpires_inreports that value as written.GET /api/auth/mereturns the signed-in admin with their roles and permissions.Call a protected route
Terminalcurl http://localhost:8000/api/bookings/manage -H "Authorization: Bearer YOUR_TOKEN"
- An unknown email and a wrong password both answer
401with the same message. - Ten failed sign-ins from one address within 15 minutes answer
429until the oldest failure is 15 minutes old (RATE_LIMIT_LOGIN). Successful sign-ins are not counted. - There is no sign-up and no sign-out route: accounts come from the seed or from Admin management, and a client signs out by forgetting its token.
- Guests have no accounts. A guest's booking routes take the booking's
access_tokenas?token=, or the email it was made with as?email=; a wrong pair answers404.
| Method | Path | Who can call it | What it does |
|---|---|---|---|
POST | /api/auth/login | Anyone | Admin sign-in: email, password |
GET | /api/auth/me | Any signed-in admin | The signed-in admin, their roles and permissions |
Permissions
Dashboard routes check a permission named <area>.<action>. An admin holds every permission of every role they have, and a route that names several accepts any one of them. The 32 permissions and the two seeded roles are listed in Admins, roles and permissions.
Stays on the website
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/stays | Anyone | Published stays. destination (slug), checkIn and checkOut (YYYY-MM-DD, both or neither: only stays free for every night), guests (the fewest it must sleep), type (villa, apartment, cabin, chalet, loft, cottage), sort (recommended, price_asc, price_desc, rating), page, page_size |
GET | /api/stays/featured | Anyone | The home page's featured stays: the references in the home_featured_references setting, in order, published ones only |
GET | /api/stays/hero | Anyone | The stay named by home_hero_stay_reference, or null |
GET | /api/stays/facets | Anyone | Published stays counted by type and by destination, for the filters and the home page |
GET | /api/stays/lookup | Anyone | Published stays by id, for saved stays: ?ids=1,2,3, at most 12 |
GET | /api/stays/:slug | Anyone | One published stay in full, with its photographs, amenities, house rules and destination |
GET | /api/stays/:slug/similar | Anyone | Up to three similar published stays |
GET | /api/stays/:slug/availability | Anyone | The nights already held between from and to, 92 days at most |
GET | /api/stays/:slug/quote | Anyone | checkIn, checkOut, guests (1 to 50): nights, nightly rate, subtotal, cleaning fee, total, available and the refusal reason (past, min_nights, capacity, taken) |
GET | /api/stays/:slug/reviews | Anyone | The stay's published reviews, newest first, with its average |
Destinations, questions and the company's settings
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/destinations | Anyone | The destinations marked visible, in their sort order |
GET | /api/destinations/:slug | Anyone | One destination |
GET | /api/faq | Anyone | The home page's questions and answers, in order |
GET | /api/settings/public | Anyone | The settings the website renders: company_name, the tagline and description in both languages, contact_email, contact_phone, contact_address_hint, currency, timezone, the default check-in and check-out times, allow_pay_on_arrival, cancellation_policy, home_featured_references, home_hero_stay_reference and service_email_signature |
GET | /api/payments/methods | Anyone | What the checkout may offer, in priority order: { methods: [{ method, hosted }], allowPayOnArrival, simulated }. Only configured processors' methods are listed, on_arrival while its setting is on, and simulated is true while the simulator takes card and PayPal because no processor is configured |
Bookings
| Method | Path | Who can call it | What it does |
|---|---|---|---|
POST | /api/bookings | Anyone | The checkout: stay_slug, check_in, check_out, guests, guest_first_name, guest_last_name, guest_email, payment_method (card, paypal, on_arrival), client_token (8 to 64 characters), and optionally guest_phone, special_requests, locale (en, ar) and, for the simulator only, card_number, card_name, card_expiry (MM/YY), card_cvc. Answers 201 with { reference, access_token, status, payment_status, payment_method, total, currency, redirect_url, failure_reason } |
GET | /api/bookings/:reference | Anyone with the booking's token or email | The guest's booking, with can_cancel and can_review |
POST | /api/bookings/:reference/settle | Anyone with the booking's token or email | Called by the return page: captures an approved PayPal order, and answers the booking's current state for Stripe. Safe to call repeatedly |
POST | /api/bookings/:reference/cancel | Anyone with the booking's token or email | The guest cancels, while pending or confirmed and at least seven days before local check-in; later answers 422 with cancellation_window_passed |
POST | /api/bookings/:reference/review | Anyone with the booking's token or email | Review a completed booking, once: rating (1 to 5, halves allowed), comment (10 to 2000 characters) |
POST | /api/webhooks/payments/:provider | The provider, by its signature | stripe or paypal. Every verified event answers 200; a bad signature answers 401, an unknown provider 404 |
- A repeated
client_tokenreturns the first booking's receipt instead of making a second one, and a filledcompanyfield is treated as a bot: the answer looks the same, and nothing is stored. - A
payment_methodthatGET /api/payments/methodsdoes not list (PayPal with only Stripe configured, card with only PayPal, pay on arrival with its setting off) answers422withpayment_method_unavailable, before anything is written. - The nights are checked when the booking is written: held nights answer
409withdates_taken, and a stay's minimum, its capacity or a past check-in422with the reason. - A hosted method answers
pendingwith aredirect_urlto Stripe or PayPal; the booking is confirmed only by the processor's webhook or the PayPal capture.
Questions to the host
| Method | Path | Who can call it | What it does |
|---|---|---|---|
POST | /api/inquiries | Anyone | The contact form and the stay page's question dialog: name, email, message (10 to 4000 characters), client_token, and optionally phone, stay_slug (a published stay), check_in, check_out, guests |
- A repeated
client_tokenreturns the first submission instead of storing a second one, and a filledcompanyfield is treated as a bot. - Each address may send five bookings, five questions and five reviews in ten minutes; the next answers
429.
Managing stays
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/stays/manage | Admins with stays.view | Every stay, drafts included. status, type, destination, guests, bedrooms, nights (the longest minimum), price (the highest nightly rate), sort (updated, rate_asc, rate_desc), q, page, page_size |
GET | /api/stays/manage/:id | Admins with stays.view | One stay in full |
POST | /api/stays/manage | Admins with stays.create | Create a draft. Required: title, short_description, description and location_hint (en, ar), type, destination_id, bedrooms, beds, bathrooms, max_guests, nightly_rate, cleaning_fee, min_nights |
PUT | /api/stays/manage/:id | Admins with stays.edit | Change any field, the gallery included (media, up to 30 items) |
PATCH | /api/stays/manage/:id/status | Admins with stays.publish | Set status: draft or published. Publishing needs three photographs and both languages |
PUT | /api/stays/manage/:id/media | Admins with stays.edit | Replace the gallery, in order; position one is the cover |
DELETE | /api/stays/manage/:id | Admins with stays.delete | Delete the stay; refused with 409 while a booking that is not cancelled exists |
The calendar and blocked dates
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/calendar | Admins with calendar.view | The month view: month (YYYY-MM) and optionally stay |
GET | /api/availability/blocks | Admins with calendar.view | Every block, soonest first |
POST | /api/availability/blocks | Admins with calendar.block | Block nights: stay_id, from, to (exclusive, like a check-out), and optionally reason (maintenance, owner_use, other) and note. A conflict answers 409 with code dates_taken and details: { held_by, reference }, naming the booking or block in the way |
PUT | /api/availability/blocks/:id | Admins with calendar.block | Change a block |
DELETE | /api/availability/blocks/:id | Admins with calendar.block | Remove a block |
Bookings, inquiries and reviews
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/bookings/manage | Admins with bookings.view | Bookings. status, from and to (check-in range), q, stay, payment, method, guests, sort (newest, check_in, total), page, page_size; with counts per status |
GET | /api/bookings/manage/:reference | Admins with bookings.view | One booking with its timeline and the actions allowed now, each with its reason when it is not |
PATCH | /api/bookings/manage/:reference | Admins with bookings.edit | action (confirm, check_in, complete, cancel) and admin_notes. A step from the wrong status answers 422 with its reason |
GET | /api/inquiries | Admins with inquiries.view | Inquiries. status, q, from, to, stay, page, page_size; with counts per status |
GET | /api/inquiries/:id | Admins with inquiries.view | One inquiry with its stay |
PATCH | /api/inquiries/:id | Admins with inquiries.edit | Set status (new, replied, closed) and admin_notes |
GET | /api/reviews/manage | Admins with reviews.view | Reviews. status, stay (id or slug), rating (a minimum), q, since (7, 30, 90, 365 days), page; with counts |
PATCH | /api/reviews/manage/:id | Admins with reviews.moderate | Set status: published or hidden |
GET | /api/overview | Admins with overview.view | The overview: this month's cards, today's arrivals and departures, pending and latest bookings, top stays, guest countries, bookings by status, latest reviews and open inquiries |
GET | /api/overview/series | Admins with overview.view | Bookings, revenue and occupancy for each of the last twelve months |
Destinations, questions and settings
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/destinations/manage | Admins with destinations.view | Every destination, hidden ones included |
POST | /api/destinations/manage | Admins with destinations.create | Add one: name, tagline and description (en, ar), and optionally slug, cover_url, cover_alt, sort_order, is_active |
PUT | /api/destinations/manage/:id | Admins with destinations.edit | Change one |
DELETE | /api/destinations/manage/:id | Admins with destinations.delete | Delete one; refused while stays belong to it |
GET | /api/faq/manage | Admins with settings.view | Every home page question, hidden ones included |
POST | /api/faq/manage | Admins with settings.edit | Add one: question and answer (en, ar), optionally sort_order and is_active |
PUT | /api/faq/manage/:id | Admins with settings.edit | Change one |
DELETE | /api/faq/manage/:id | Admins with settings.edit | Delete one |
GET | /api/settings | Admins with settings.view | Every setting. search, category, page, page_size or page_count |
GET | /api/settings/payments-status | Admins with settings.view | { stripe: { configured, testMode }, paypal: { configured, sandbox }, simulator }: whether each processor is configured and test or live, and whether the simulator is in use, which it is only while no processor is configured. Never a key |
GET | /api/settings/:key | Admins with settings.view | One setting by key |
PATCH | /api/settings/:key | Admins with settings.edit | Change one setting's value |
DELETE | /api/settings/:key | Admins with settings.edit | Delete one setting |
Admins and roles
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/admins | Admins with admins.view | Admins. email, name, phone, page, page_count |
GET | /api/admins/statistics | Admins with admins.view | Counts for the admin list |
GET | /api/admins/:id | Admins with admins.view | One admin, by id or username |
POST | /api/admins | Admins with admins.create | Add an admin: first_name, last_name, email, password and password_confirmation (6 characters or more), optionally phone |
PATCH | /api/admins/:id | Admins with admins.edit | Change an admin |
PATCH | /api/admins/:id/roles | Admins with admins.assign_roles | Set an admin's roles: role_ids |
DELETE | /api/admins/:id | Admins with admins.delete | Delete an admin |
PATCH | /api/admins/profile | Any signed-in admin | Change the signed-in admin's own first_name, last_name, email, phone or profile_picture. The password has its own route |
PATCH | /api/admins/profile/password | Any signed-in admin | Change the signed-in admin's own password: current_password, password, password_confirmation |
GET | /api/roles | Admins with roles.view | Roles. name, guard_name, created_from, created_to, page, page_count |
GET | /api/roles/statistics | Admins with roles.view | Counts for the roles list |
GET | /api/roles/select | Admins with roles.view | Every role's id and name, for a picker |
GET | /api/roles/permissions | Admins with roles.view | Every permission, grouped by area |
GET | /api/roles/:id | Admins with roles.view | One role with its permissions |
POST | /api/roles | Admins with roles.create | Add a role |
PUT | /api/roles/:id | Admins with roles.edit | Rename or describe a role |
POST | /api/roles/:id/permissions | Admins with roles.assign_permissions | Set a role's permissions |
DELETE | /api/roles/:id | Admins with roles.delete | Delete a role |
Notifications and uploads
| Method | Path | Who can call it | What it does |
|---|---|---|---|
GET | /api/notifications | Any signed-in admin | The signed-in admin's newest 30 notifications |
PATCH | /api/notifications/read-all | Any signed-in admin | Mark them all read |
PATCH | /api/notifications/:id/read | Any signed-in admin | Mark one read |
DELETE | /api/notifications/:id | Any signed-in admin | Remove one |
POST | /api/helpers/upload | Any signed-in admin | Store a file (multipart file, with path for the folder, for for the size preset such as stay, cover or profile, and type). Photographs are re-encoded as JPEG, at most 1920 pixels wide; videos are accepted as MP4, WebM, MOV or M4V; 150 MB at most. Answers the stored file's addresses |
An upload has no permission of its own: what the file becomes is checked by the route that saves it. Notifications are kept for 30 days.
Limits per address
| Route | Limit | Variable |
|---|---|---|
POST /api/auth/login | 10 failed sign-ins per 15 minutes | RATE_LIMIT_LOGIN |
POST /api/bookings | 5 per 10 minutes | None |
POST /api/inquiries | 5 per 10 minutes | None |
POST /api/bookings/:reference/review | 5 per 10 minutes | None |
A request over a limit answers 429. The counts live in the API's memory, so a restart clears them. Behind a reverse proxy, set TRUST_PROXY so each visitor is counted on their own address.
AI assistant and its conversations
Included with your purchase. Sign in to read, or open it in your download.
The assistant's streaming route, its model list and the saved conversations.
MCP server
Included with your purchase. Sign in to read, or open it in your download.
The MCP endpoint for coding agents and how it is authorised.
Live updates over WebSocket
Included with your purchase. Sign in to read, or open it in your download.
The Socket.IO namespaces the dashboard listens on, their events and how a connection is authorised.
Demo mode routes
Included with your purchase. Sign in to read, or open it in your download.
The routes that exist only for a public demo build.