Skip to content

How to build: owner — set up a property

Winner: Core for property / room / rate_plan / metadata. Middleware only for image upload and (later) Google push.

Hotels, shortlets, and apartments use the same create-property URL. Set property_category in the next step (hotel | shortlet | apartment). Apartment sold by unit → rooms + rate plans. Apartment sold as a whole → shortlet pattern.

Login → Create property (draft) → Metadata + amenities → Rooms → Rate plans → Upload photos → Publish (status: active)

Entity types and extra_data keys: Entities and Metadata catalog.


1. Create property

Core · winner

Call this API

POST https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property

Path: POST /public/api/v1/nellalink/smart-meta-manager/entity/property

Explorer: Core — Smart Meta Manager

Headers

http
x-api-key: {API_KEY}
Authorization: Bearer {access_token}
Content-Type: application/json

Request

json
{
  "request_id": "prop-abc-hotel-001",
  "meta_key": "abc_hotel_lagos",
  "slug": "abc-hotel-lagos",
  "title_name": "ABC Hotel Lagos",
  "description": "A 10-room boutique hotel in Victoria Island.",
  "owned_by": "{session_user_uuid}",
  "status": "draft",
  "extra_data": {
    "gallery_urls": [],
    "total_rooms_display": 10
  },
  "categories": [{ "name": "Hotel" }],
  "tags": ["hotel"]
}

owned_by must be the logged-in user’s uuid. request_id must be unique (use it to ignore double-clicks).

Save from response: data.uuid{property_uuid}.

For a shortlet or apartment, use the same endpoint; set property_category to shortlet or apartment in the next step.

How to build: send owned_by = session uuid. Core must reject a JWT mismatch (require_user_bearer_token + owner check on this POST path). If that is missing, recommend it on Core path settings — not as a Vue IDOR task.

If Middleware later owns “upload property”

A future POST https://middleware-dev.bookdirect.live/api/v1/bookdirect/properties would become the winner. This Core URL would move to “Called by Middleware, not by frontend.” Propose that on /next — do not change v1 until that route is live.


2. Add location and details (metadata)

Core · winner

Call this API

POST https://backend-dev.bookdirect.live/public/api/v1/nellalink/meta-data/entity/property/{property_uuid}

Send an array for several keys:

json
[
  { "meta_key": "property_category", "meta_value": "hotel", "data_type": "string" },
  { "meta_key": "address", "meta_value": "12 Admiralty Way, Victoria Island", "data_type": "string" },
  { "meta_key": "city", "meta_value": "Lagos", "data_type": "string" },
  { "meta_key": "state", "meta_value": "Lagos", "data_type": "string" },
  { "meta_key": "country", "meta_value": "Nigeria", "data_type": "string" },
  { "meta_key": "latitude", "meta_value": "6.4281", "data_type": "string" },
  { "meta_key": "longitude", "meta_value": "3.4219", "data_type": "string" },
  { "meta_key": "check_in_time", "meta_value": "14:00", "data_type": "string" },
  { "meta_key": "check_out_time", "meta_value": "11:00", "data_type": "string" }
]

Shortlet: "meta_value": "shortlet". Apartment: "meta_value": "apartment". Hotel: "hotel".


3. Amenities (tick boxes)

Same metadata endpoint. One row per ticked amenity. Full table: Metadata catalog.

json
[
  { "meta_key": "amenity_swimming_pool", "meta_value": "true", "data_type": "boolean" },
  { "meta_key": "amenity_free_parking", "meta_value": "true", "data_type": "boolean" }
]

To untick: PUT the same key with "meta_value": "false".

Optional mirror on the property for easy UI read:

Core · winner

PUT https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property/{property_uuid}

extra_data replaces the whole object. Always send the full object.


4. Create a room (hotel)

Core · winner

Call this API

POST https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/room

json
{
  "request_id": "room-abc-standard-001",
  "meta_key": "standard_room",
  "slug": "standard-room",
  "title_name": "Standard Room",
  "description": "Queen bed, city view.",
  "parent_entity_type": "property",
  "parent_entity_uuid": "{property_uuid}",
  "owned_by": "{session_user_uuid}",
  "status": "active",
  "extra_data": {
    "total_units": 3,
    "room_numbers": ["RM1", "RM2", "RM3"],
    "max_occupancy": 2,
    "bed_type": "queen"
  }
}

Save data.uuid{room_uuid}. Repeat per room type (Deluxe, VIP).

The old desktop guide’s booking_package is this room plus a rate_plan.


5. Create a rate plan

Core · winner

Call this API

POST https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/rate_plan

json
{
  "request_id": "rp-abc-standard-flex-001",
  "meta_key": "standard_flexible",
  "slug": "standard-flexible",
  "title_name": "Flexible rate",
  "parent_entity_type": "room",
  "parent_entity_uuid": "{room_uuid}",
  "owned_by": "{session_user_uuid}",
  "status": "active",
  "extra_data": {
    "base_rate_ngn": 30000,
    "currency": "NGN",
    "cancellation": "free_24h",
    "breakfast_included": false
  }
}

Save {rate_plan_uuid}. Google ARI later keys off room + rate_plan.


6. Upload photos (Middleware)

Middleware · winner

Call this API

POST https://middleware-dev.bookdirect.live/api/v1/nellalink/file-manager/aws/upload-url

Already live. Explorer: Middleware docs

http
Authorization: Bearer {access_token}
Content-Type: application/json
json
{
  "files": [
    { "file_name": "cover.jpg", "file_type": "image/jpeg", "file_size": 240000, "visibility": "public" }
  ]
}

PUT the file bytes to the returned URL, then PUT the property entity_featured_url / extra_data.gallery_urls on Core.


7. Publish

Core · winner

PUT https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property/{property_uuid}

json
{ "status": "active" }

After publish, owner tools may call Google rates push (Middleware). Frontend does not talk to GuestTraction directly.


8. List my properties

Core · winner

GET https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property?owned_by={session_user_uuid}&per_page=20&page=1

How to build the dashboard list: pass owned_by={session_user_uuid} so the UI shows the owner’s rows. Core must still scope the write key + JWT so a public browse key cannot list another owner’s drafts or all bookings.

Rooms for a property:

GET .../entity/room?parent_entity_type=property&parent_entity_uuid={property_uuid}&per_page=50

Rate plans for a room:

GET .../entity/rate_plan?parent_entity_type=room&parent_entity_uuid={room_uuid}


Shortlet or whole apartment (no rooms)

  1. Create property as above.
  2. Metadata property_category = shortlet or apartment.
  3. Put inventory and price on the property extra_data:
json
{
  "total_units": 1,
  "base_rate_ngn": 150000,
  "currency": "NGN",
  "max_occupancy": 6
}

Bookings use parent_entity_type: "property". See Guest search and book.


Owner checklist

  • [ ] Login → token + uuid
  • [ ] POST property (draft)
  • [ ] POST metadata (location, category, times)
  • [ ] POST amenity metadata
  • [ ] POST each room + rate_plan (hotel / apartment-by-unit) or extra_data rates (shortlet / whole apartment)
  • [ ] Upload images via Middleware file-manager
  • [ ] PUT property status: active
  • [ ] GET property + children to verify

API-first. Middleware wins over Core when both exist.