Skip to content

Search indexes — categories, tags, metadata ​

Job: what Middleware Backend writes on a Core property so Marketplace search can narrow in one Core list call, then finish price / dates / rooms / guests itself.

Frontend never calls these URLs. Winner for the guest is still Middleware properties. Contract for the list: fetchAll (same path on BookDirect Core).

tags=a,b is OR (whereIn). search_by + tags + categories together are AND. Query one tag family per Core call. City stays search_by=meta_key.city so it ANDs with amenities.


What lives where ​

StoreWhatQuery
Category (one)hotel | shortlet | apartmentcategories=hotel
Tagsamenity_{slug}, optional mirrors location_city_{slug}, policy_*, star_rating_{n}, rules_minimum_stay_{n}tags=amenity_swimming_pool — one prefix family
Linked metadatacity, amenity_{slug}=true, min_rate_ngn, lat/lng, check_in_timesearch_by=meta_key.city&search=Lagos
MiddlewareSecond+ amenity AND, price range/sort, check-in/out, units_needed, guests vs maxOccupancyAfter the Core narrow

Do not tag stay dates or “rooms free.” Do not tag rules_check_in_14_00 / rules_check_out_12_00 — those stay check_in_time / check_out_time metadata.

Pet: one slug — amenity_pet_friendly. Do not also write rules_pet_allowed.


Middleware writes on create / update ​

Called by Middleware Backend, not by frontend.

  1. PUT / POST Core entity extra_data (full object): city, amenities: ["swimming_pool"] (slugs), min_rate_ngn, star_rating, policy.
  2. Linked metadata — one row per tick:

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

json
[
  { "meta_key": "city", "meta_value": "Lagos", "data_type": "string" },
  { "meta_key": "amenity_swimming_pool", "meta_value": "true", "data_type": "string" },
  { "meta_key": "min_rate_ngn", "meta_value": "30000", "data_type": "string" }
]
  1. Tags (names ^[a-z0-9_]+$). Assignment is additive — remove stale location_city_* / amenity tags when city or ticks change.

POST https://backend-dev.bookdirect.live/public/api/v1/nellalink/tag then tags: [...] on the entity.

TagMirror of
amenity_swimming_pooltick
location_city_lagoscity slug (Lagos → lagos)
policy_free_cancellationpolicy.freeCancellation
policy_refund_allowedextra_data
policy_group_booking_allowedextra_data
policy_caution_fee_fully_refundableextra_data
star_rating_5star_rating (optional)
rules_minimum_stay_1policy.minStayNights (optional)

Category: categories: [{ "name": "Hotel" }] → slug hotel.

Recompute min_rate_ngn when rates change (min of active plans, or shortlet base_rate_ngn).

IDOR: Middleware uses the server Core key + owner JWT on writes. Do not let the public browse key POST tags or metadata.


Example queries (Middleware → Core) ​

Host in playbooks: https://backend-dev.bookdirect.live/public/api/v1/nellalink. Same fetchAll as Nellalink test explorer.

Use this instead of listing every property.

http
GET https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property?search=Lagos&search_by=meta_key.city&tags=amenity_swimming_pool&categories=hotel&meta_key_to_retrieve=city,min_rate_ngn,amenity_swimming_pool,amenity_free_parking&per_page=20&page=1
bash
curl -sS -G 'https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property' \
  --data-urlencode 'search=Lagos' \
  --data-urlencode 'search_by=meta_key.city' \
  --data-urlencode 'tags=amenity_swimming_pool' \
  --data-urlencode 'categories=hotel' \
  --data-urlencode 'meta_key_to_retrieve=city,min_rate_ngn,amenity_swimming_pool,amenity_free_parking' \
  --data-urlencode 'per_page=20' \
  --data-urlencode 'page=1' \
  -H 'Accept: application/json' \
  -H 'x-api-key: {ROUTE_FILE_MANAGER_X_API_KEY_CORE_BACKEND}'

Then AND extra amenities in process: keep rows whose meta_data.amenity_free_parking is "true" (or extra_data.amenities contains free_parking). Filter/sort min_rate_ngn. Dates / units_needed / guests: availability on those UUIDs.

ts
const slugs = ["swimming_pool", "free_parking"]; // from ?amenities=
const [narrowTag, ...andSlugs] = slugs;

const qs = new URLSearchParams({
  search: "Lagos",
  search_by: "meta_key.city",
  tags: `amenity_${narrowTag}`,
  categories: "hotel",
  meta_key_to_retrieve: ["city", "min_rate_ngn", ...slugs.map((s) => `amenity_${s}`)].join(","),
  per_page: "20",
  page: "1",
});

const res = await fetch(
  `https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property?${qs}`,
  { headers: { Accept: "application/json", "x-api-key": process.env.ROUTE_FILE_MANAGER_X_API_KEY_CORE_BACKEND! } },
);
const page = await res.json();

const rows = (page.data ?? []).filter((row) =>
  andSlugs.every(
    (s) =>
      row.meta_data?.[`amenity_${s}`] === "true" ||
      (row.extra_data?.amenities ?? []).includes(s),
  ),
);
// then min_rate_ngn range/sort; availability/check for dates/rooms; drop maxOccupancy < guests

Do not listAll every property. Do not send tags=amenity_swimming_pool,amenity_free_parking expecting AND.

2. City only (destination page) ​

http
GET https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property?search=Lagos&search_by=meta_key.city&categories=hotel&per_page=20&page=1

3. City AND one policy (no amenity) ​

http
GET https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property?search=Lagos&search_by=meta_key.city&tags=policy_free_cancellation&per_page=20

4. OR cities (use location tags — do not mix amenity tags here) ​

http
GET https://backend-dev.bookdirect.live/public/api/v1/nellalink/smart-meta-manager/entity/property?tags=location_city_lagos,location_city_uyo&categories=hotel&per_page=20

5. Do not use (location OR amenity) ​

http
GET .../entity/property?tags=location_city_lagos,amenity_swimming_pool

Returns Lagos or pool. Wrong for Marketplace.


After Core: Middleware only ​

Guest paramNot CoreMiddleware
Second amenity ANDtags=a,b is ORamenity_* / extra_data.amenities slugs
minPrice / maxPrice / price sortno numeric sort_bymin_rate_ngn (or cheapest plan until denormalized)
check_in / check_outno overlap enginePOST .../availability/check
units_neededunits on roomavailable >= units_needed
guestsdrop plans with maxOccupancy < guests

Frontend still calls one Middleware URL. Tick-list slugs: Frontend amenity catalog.

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