{
  "api_version": "2026-08-04",
  "api_version_date": "2026-08-04",
  "version_header": "X-API-Version",
  "endpoint": "/ai/v1/search",
  "method": "POST",
  "session": {
    "endpoint": "/ai/v1/session",
    "method": "POST",
    "description": "Opens a session. No credentials or registration are required — the request carries at most the entry source. An entry that names a source (https://nexawebsite.digital/partners/{source}) sends it as the request body; the source is stored on the session, is not re-supplied by any later request, and pre-populates the search payload. /ai/v1/auth is a permanent alias of this endpoint: it answers 308 with this endpoint's address.",
    "request_fields": {
      "source": "the brand the session entered from, when the entry named one"
    },
    "response_fields": {
      "object": "\"session\"",
      "session": "the session token every later request carries",
      "source": "the entry source stored on this session, or null",
      "expires_at": "UTC ISO-8601 — when the session stops being valid",
      "next_request": "{ url } — the first search request (POST /ai/v1/search), sent by opening its url. next_request objects carry no method key; the documented endpoint methods apply."
    }
  },
  "listing_entry": {
    "entry": "https://nexawebsite.digital/partners/{source}/{id}/{title}",
    "entry_by_id": "https://nexawebsite.digital/partners/{source}/{id} — answers 308 to the entry above; {id} is the listing's api_id and is all that is needed",
    "description": "A session entered from one property page. The entry URL carries the whole entry state (source brand and listing id). The session request body carries source and property; both are echoed in received from the first frame and never re-sent. The flow asks only for the stay: field_order is checkin_month, checkin, checkout, accommodates — no destination, no brand_locked, no property step. The validation cycle, envelopes and idempotency are identical to the search flow.",
    "field_order": [
      "checkin_month",
      "checkin",
      "checkout",
      "accommodates"
    ],
    "result": "one property: the bound listing with its live total for the stay, and its own listing page on the operator's site — carrying the stay parameters — as the one user-facing link.",
    "unknown_listing": "an unknown listing id at the entry returns HTTP 200 with a factual page stating the property is not on the network, and the main entry"
  },
  "entry_modes": [
    {
      "mode": "direct",
      "entry": "https://nexawebsite.digital/",
      "binds": "nothing",
      "field_order": [
        "destination",
        "checkin_month",
        "checkin",
        "checkout",
        "accommodates"
      ],
      "brand_locked": "not asked — no source was carried"
    },
    {
      "mode": "operator search",
      "entry": "https://nexawebsite.digital/partners/{source}",
      "binds": "the operator, as the session's source",
      "field_order": [
        "brand_locked",
        "destination",
        "checkin_month",
        "checkin",
        "checkout",
        "accommodates"
      ],
      "brand_locked": "asked — true keeps the search to the source operator, false searches the network"
    },
    {
      "mode": "destination search",
      "entry": "https://nexawebsite.digital/?destination={slug}",
      "binds": "the destination, with no source",
      "field_order": [
        "checkin_month",
        "checkin",
        "checkout",
        "accommodates"
      ],
      "brand_locked": "never asked — this binds a destination, not a brand, and a destination may hold several operators' properties",
      "unknown_slug": "HTTP 200 with error: unknown_destination and start_url, never a 404 and never a redirect"
    }
  ],
  "description": "Validation cycle: every response is HTTP 200 and includes the next request you can send (next_request). An incomplete payload's body carries error: incomplete_payload with received, missing_fields, allowed_values, next_required, and available_requests (the requests that complete the next field). A complete payload lands on the filters state (see filters_state): every amenity and property type that would narrow the set, each with its count, and get_results. Sending get_results lands on the completed-search state (see completed_search), which carries the result set. No state advances on its own.",
  "field_order": [
    "brand_locked",
    "destination",
    "checkin_month",
    "checkin",
    "checkout",
    "accommodates"
  ],
  "depends_on": {
    "destination": "brand_locked",
    "checkin": "checkin_month",
    "checkout": "checkin"
  },
  "fields": {
    "brand_locked": {
      "type": "boolean",
      "required": true,
      "conditional": "Present only for operator-scoped sessions — those whose session request carried a source. A session entered directly has no source brand to lock to: its payload has no brand_locked field, its flow starts at destination, and it searches every brand on the network.",
      "description": "Search scope. true limits results to the brand the session entered from (the source carried on the session); false returns results from every brand on the Nexa AI network. allowed_values carries both values with the condition each one holds under; the operator display name is filled in from the session's source.",
      "allowed_values": [
        {
          "value": true,
          "description": "The user asked for this brand specifically (<operator display name>)."
        },
        {
          "value": false,
          "description": "The user did not ask for a specific brand."
        }
      ]
    },
    "destination": {
      "type": "string",
      "required": true,
      "description": "Destination slug (allowed_values enumerates the options), drawn from the session's search scope — the brand_locked choice on operator-scoped sessions, the whole network otherwise."
    },
    "checkin_month": {
      "type": "string",
      "required": true,
      "format": "YYYY-MM",
      "constraint": "a month the network can serve — the offered months run from the current one to the last month any operator publishes availability in, and a month with no selectable day in that range is not offered"
    },
    "checkin": {
      "type": "string",
      "required": true,
      "format": "YYYY-MM-DD",
      "depends_on": "checkin_month",
      "constraint": "today onward, and not past the last date any operator publishes availability for"
    },
    "checkout": {
      "type": "string",
      "required": true,
      "format": "YYYY-MM-DD",
      "depends_on": "checkin",
      "constraint": "1-30 nights after checkin, stopping at the last check-out the published availability supports (a stay's last night is checkout minus one, so that is the published end plus one day)"
    },
    "accommodates": {
      "type": "integer",
      "required": true,
      "minimum": 1,
      "maximum": 12,
      "description": "The party size. Maveriks' filter attribute: a listing matches when its accommodates is at least this value."
    }
  },
  "errors": {
    "incomplete_payload": "one or more required fields missing — allowed_values and available_requests carry what completes the payload, and next_request is the first of them",
    "unknown_session": "the session is not known or has expired — start_url begins a new one",
    "unknown_listing": "no property with that id is on this network for that brand — the envelope names the condition and the id, and start_url opens a session at the main entry",
    "unknown_destination": "the destination slug is not one this network serves — the body names the slug, and start_url opens a session at the main entry. Never a 404 and never a redirect."
  },
  "authentication": "There is no authentication condition. Access is decided once, at the network edge, before any endpoint runs; no request that reaches an endpoint is refused for authentication, no credential is ever requested, and no response carries an authentication error. The session request always opens a session.",
  "no_matches": {
    "flow": "the search still completes: received is full, next_required is null, and results is an empty array. message states the condition — which constraint emptied the set and the nearest state that is not empty (no_matches.constraint, no_matches.nearest_alternative) — alongside a new search. No re-ordering is offered: re-ranking an empty set would be offering nothing.",
    "bound_property": "a session bound to one property (the listing entry) returns that property or nothing. It never substitutes the operator's other properties for the one asked about.",
    "reachability": "the refine steps cannot reach it. Every offered value carries its count over the current set and only values matching at least one property are offered, so no sequence of choices this flow allows empties the set. A zero-match page is reached by editing a results URL, not by using the flow."
  },
  "recovery_states": {
    "object": "the recovery state names itself in object, alongside the session object: object: \"restart\".",
    "start_url": "https://nexawebsite.digital/ — the plain entry. Identical in every state that carries it (unknown_session, session_completed, unknown_listing and the state below), and never derived from the session that ended: a token that could not be read is no evidence of where the request came from.",
    "restart": "a request whose idempotency_key could not be read — truncated, edited, or minted by an earlier format. Nothing is wrong and nothing is refused; the envelope carries start_url and the page carries the first request."
  },
  "result_actions": {
    "description": "The completed search (see completed_search) carries two refine requests and one sort request per ranking not in force. There is no request that changes a choice already made — a different search is a new session, one request away at start_url.",
    "refine": {
      "amenities": "the amenity step, reached from the stay-complete state. ADDITIVE and multi-select: each value sent narrows the set with AND. Counts are computed over the CURRENT set — every filter already applied, the property type included.",
      "property_type": "the property-type step, reached from the stay-complete state. SINGLE-SELECT, not additive: nothing is both a studio and a villa, so a new value REPLACES the previous one, and a row carrying null clears the filter. COUNTS ARE COMPUTED OVER THE MATCH SET BEFORE THE TYPE FILTER — destination, dates, guests and amenities applied, the type itself not — so every other type keeps a real count while one is in force and the session can always switch.",
      "discrimination": "a value is offered only while it discriminates: values already applied, values matching nothing, and values every current match has are all omitted. This is why no sequence of choices this flow allows can reach an empty set.",
      "removal": "each filter in force carries its own request that drops just that one, so a narrowing is reversible without restarting."
    },
    "sort": {
      "description": "the orderings of the WHOLE match set, as requests on the session: sending one re-ranks every match and returns the stay-complete state with the top of that order. The ranking in force is omitted, and a set with fewer than two matches offers none.",
      "values": {
        "price": "the DEFAULT — base price, lowest first; rating breaks ties (Maveriks' sort token)",
        "-rating": "highest rated first; a property without a rating sorts after every property with one (Maveriks' sort token: a leading minus is descending)"
      },
      "applies_to": "ALL MATCHES, not the options shown. Ordered by rating, the page carries the top-rated of every match — it does NOT reorder the ones the default ordering showed, so the properties themselves change. The number of matches is unchanged by a re-ordering."
    }
  },
  "actions": {
    "description": "Each response presents the requests available at that point as controls — each one complete and ready. The client assembles nothing, and no request outside the presented set is part of the exchange. Activating a control performs the request it carries and returns the next state. Each control states what it does and, where it sets a value, the value it sets.",
    "equivalence": "A control may be activated in whatever way the client can act — followed, submitted, or sent as the documented request. These are the same request and return the same result, so a client that can act on what it is given needs nothing further to complete the exchange.",
    "bounds": "Identifiers here are short by design: no value within an action exceeds 90 characters, and no action exceeds 200 characters in total."
  },
  "versioning": {
    "description": "The API version is a date, returned on every response as api_version and as the X-API-Version header. A change that is not backward-compatible is issued under a new date. Every response states the version that produced it.",
    "history": "No changelog, no archive, no list of past versions."
  },
  "session_bounds": {
    "session": "every request carries the session token; there is no state outside it",
    "idempotency": "replaying a key returns the outcome that key produced rather than executing again",
    "expiry": "a 30-minute sliding window, refreshed by every request on the session, so an active conversation never expires"
  },
  "filters_state": {
    "endpoint": "/ai/v1/search",
    "status": 200,
    "description": "Reached when the fifth field is sent. state is \"filters\", error is null, missing_fields is empty and next_required is null. allowed_values.amenities lists every amenity that would narrow the set (multi-select), each with match_count over the CURRENT matches; allowed_values.property_type lists every type that would (single-select), each with match_count over the set before the type filter. Sending a value returns to this state, narrower, with the counts recomputed. next_request is get_results — the set as it stands — and it is the only way on. The state is skipped, straight to completed_search, when no value would narrow the set (a session bound to one listing, for example).",
    "fields": {
      "state": "\"filters\"",
      "match_count": "how many listings match the stay with the filters in force",
      "allowed_values": "{ amenities: [{ value, label, match_count, of }], property_type: [{ value, label, match_count, of }] }",
      "field_order": "the search fields, then amenities, property_type, get_results",
      "next_request": "{ action: \"get_results\", url }",
      "available_requests": "one request per amenity value ({ field: \"amenities\", value, url }), one per property type ({ field: \"property_type\", value, url }), and { action: \"get_results\", url }"
    }
  },
  "completed_search": {
    "endpoint": "/ai/v1/search",
    "status": 200,
    "description": "The completed payload AND the result set, one object. received carries every chosen value, missing_fields is empty and next_required is null; results carries the ranked options and comparison_set the counts, the ranking and the money semantics. Nothing follows: next_request is null, and the assistant relays the set. Two OPTIONAL requests narrow it, one re-ranks it, and one starts a new search.",
    "fields": {
      "message": "\"Payload complete. The result set is below.\" — or, with no matches, the condition and the nearest alternative",
      "received": "every chosen value",
      "missing_fields": "[]",
      "next_required": "null",
      "next_request": "null — the set is the answer",
      "summary": "the set in one sentence: how many match, where, for which stay, the span of the totals and the cheapest by name — with any applied filters named, because \"12 apartment options\" and \"12 options\" are different claims",
      "query": "destination, checkin_at, checkout_at, guest_count, nights_count (Maveriks' QuoteDto names) — and brand_locked where the session was asked it",
      "comparison_set": "options_count, total_matches, summary, sort (price | -rating), sort_label, comparison_fields, currency, totals_include_taxes_and_fees, quotes_valid_until, user_facing_urls",
      "results": "the ranked options — see result_set",
      "applied_filters": "{ amenities: [...], property_type }",
      "self_url": "this set again, priced now, for as long as the session lives",
      "session_expires_at": "when this session stops being valid if left idle; see session_bounds.expiry",
      "available_requests": "the amenity step, the property_type step, one remove request per filter in force, one sort request per ranking not in force, and { method: \"GET\", path: \"/\" } to start a new search. A step appears only while it has a value that would narrow the set."
    },
    "refine": "both steps are optional and neither is a rewind: each APPENDS to the request log and returns to this same state, narrower, with a remove request for each filter in force. See result_actions.refine.",
    "listing_mode": "a session bound to one property returns that property or nothing: one property is not a set, and its url is the operator's page for it."
  },
  "idempotency": {
    "parameter": "idempotency_key",
    "description": "Each request carries a unique idempotency_key. Replaying a key on the same session returns the outcome that key produced rather than executing again. The key is echoed on the response as the Idempotency-Key header.",
    "response_header": "Idempotency-Key"
  },
  "field_semantics": {
    "anchor": "#field-semantics",
    "definitions": [
      "Results are ranked by starting nightly rate, lowest first, with rating breaking ties — the default ordering; rank is the supplied order. The set offers the same matches ranked by highest rating as a sort request on the session.",
      "A stay total is the final price for the entire stay and all requested guests — including all taxes and fees. The nightly average is derived from it; the line items sum to it exactly.",
      "Each option carries the operator's own URL for that property, with the stay parameters on it — the user's booking path, and the page that server-renders the property's photos. One option, one operator URL; complete and used as returned.",
      "The amenity list on an option is the complete list for that property.",
      "A rating is the property's guest rating out of its scale, over its review count — the same figures the operator's listing page shows. A property the operator publishes no rating for carries none.",
      "A cancellation statement describes the booking being quoted, not the policy in the abstract: where the free-cancellation window has already closed for this stay it says so and names the date it closed, and where the window is open it names the date it closes. A property the operator publishes no policy for carries no cancellation statement at all.",
      "A minimum stay is stated only where the operator publishes one worth stating; a property with none carries no minimum-stay sentence.",
      "A total is checkable where it can be checked by something other than this API: the operator's own listing page, at the option's own URL, states the same total for the same stay."
    ]
  },
  "refine_request": {
    "endpoint": "/ai/v1/search",
    "method": "POST",
    "description": "The amenity step, reached from the stay-complete state. In this request context amenities is REQUIRED, so the body is the same validation cycle the search fields use: HTTP 200, error: incomplete_payload, with received, missing_fields, allowed_values, field_order, next_required, self_url, schema_url and available_requests. It APPENDS to the session — the search fields are never re-asked.",
    "body_field": "amenities",
    "required_in_this_context": true,
    "fields": {
      "received": "every field chosen so far, including amenities already applied, in send order",
      "missing_fields": "[\"amenities\"]",
      "next_required": "\"amenities\"",
      "field_order": "the five search fields, then amenities",
      "allowed_values": "{ amenities: [...] } — see allowed_values_fields",
      "available_requests": "one request per allowed value ({ field, value, url }), plus a final POST /ai/v1/search that returns to the unchanged stay-complete state without choosing an amenity — the refine is optional, and declining it is a step with its own URL like any other",
      "next_request": "the first allowed value as a ready request"
    },
    "allowed_values_fields": [
      "value",
      "label",
      "match_count",
      "of"
    ],
    "counts": "match_count is computed over the CURRENT set — every filter already applied, the property type included; `of` is that set's total. A value is offered only while it discriminates: values already applied, values matching nothing, and values every current match has (match_count === of) are omitted. When none remain the stay-complete state carries no amenity step.",
    "stacking": "One amenity per request. Filters are additive: sending one returns the stay-complete state with that amenity applied, and the step can be re-entered from there to add another. Every state is priced fresh."
  },
  "result_set": {
    "where": "completed_search.results",
    "ranking": {
      "price": "the DEFAULT — base price, lowest first; rating breaks ties",
      "-rating": "highest rated first; a property without a rating sorts after every property with one"
    },
    "vocabulary": "Every option is described in the Maveriks (Roomizer) data model: its attribute names are Maveriks' filter attributes and listing metadata, its values are Maveriks' filter labels (property types, amenities, house rules — resolved by its own synonym rule, unmatched values dropped), and its quote is Maveriks' QuoteDto. The map from each operator's API to these names is published in the repository as docs/MAVERIKS-MAPPING.md.",
    "per_option": "rank, api_id (this system's id for the listing — one per room type or apartment, across every operator), property_id, title, nickname, slug, mtl_type (parent for a room type several rooms stand behind, single for one unit), brand, property_type (a Maveriks label) and property_type_source (the operator's), accommodates and accommodates_source where the figure is the hotel's per-booking limit, bedrooms, bathrooms where published, beds where published, apartment_size (m²) where published, amenities (Maveriks labels), amenities_as_operator_lists_them (complete, the operator's own words), house_rules (Maveriks labels the operator states as allowed; no defaults), city, location { latitude, longitude } where published, summary, rank_label, base_price { amount, currency } where the operator publishes a rate card (absent where it prices by quote), stay_nights_range { min, max }, calendar_availability_window (days), description { summary, neighborhoodDescription, notes } and room { … } for a collected operator's listing, property { name, address, default_check_in, default_check_out (HH:MM), check_in_as_published, check_out_as_published, rooms_in_hotel, virtual_tour_url, guest_review_score on the operator's own scale, … }, rating and rating_count where the operator publishes a 0–5 rating, cancellation where published (summary, free_cancellation_until while the window is open), images { primary, gallery }, quote (below), and url — the operator's own page for this property and stay, the one URL meant for the person.",
    "quote": "Maveriks' QuoteDto: id, checkin_at, checkout_at, guest_count, nights_count, discount, promotion, rate_plans[] — each with id, name (the operator's own rate name), currency { code, sign }, avg_price_per_night (fare_accommodation / nights_count), fare_accommodation, fare_accommodation_adjusted, total_fees, total_taxes, sub_total_price, host_payout (THE guest total, as the operator states it), cancellation_policy { title, template, refundable, cancellable_until }, rate_plan_features (meal plan: room_only, breakfast, breakfast_dinner, fullboard, all_inclusive), security_deposit, is_available, error_messages, invoice_items[] { title, type ∈ accommodation_fare | cleaning_fee | additional | upsell | tax | discount | promotion, amount, quantity }, and selected (the plan the option's total is; first). Plans are ordered available first, then host_payout ascending. Then this system's own facts: source (live_quote | rate_card), priced_at, assumptions[], valid_until, and display { total, per_night } — host_payout and host_payout / nights_count written the way a person reads them.",
    "html_form": "the same response with Accept: text/html renders the set below the request log, one article per option, phrased for a reader without scripts; the JSON travels in the page as api-response."
  },
  "access": {
    "audience": "ai_assistants",
    "end_user_access": false,
    "flow_endpoints": "the endpoints under /ai/v1/ are addressed to the assistant — request frames and envelopes rather than pages. The result set is the completed search's own response.",
    "operator_pages": "each option's url opens that property on the operator's own website, for the stay searched, where the reservation is completed. It is the one URL meant for the person."
  },
  "maveriks": {
    "model": "Maveriks (Roomizer): filter attributes and listing metadata for the names, filter_labels for the values, QuoteDto for the quote. Keys are the attribute slug with - written as _; values are the label slugs verbatim.",
    "property_types": {
      "labels": [
        "apartment",
        "bungalow",
        "cabin",
        "chalet",
        "condominium",
        "family-house",
        "farm",
        "guesthouse",
        "hostel",
        "hotel",
        "house",
        "island",
        "loft",
        "other",
        "studio",
        "townhouse",
        "villa"
      ],
      "in_use": [
        {
          "label": "hotel",
          "count": 5031
        },
        {
          "label": "apartment",
          "count": 2302
        },
        {
          "label": "studio",
          "count": 18
        },
        {
          "label": "villa",
          "count": 16
        },
        {
          "label": "townhouse",
          "count": 14
        }
      ]
    },
    "amenities": {
      "resolution": "FilterLabel::bySynonyms(Str::slug(value)); unmatched values are dropped, no label is created",
      "in_use": [
        {
          "label": "tv",
          "title": "TV",
          "count": 6466
        },
        {
          "label": "laptop-friendly-workspace",
          "title": "Laptop friendly workspace",
          "count": 5608
        },
        {
          "label": "wireless-internet",
          "title": "Wireless Internet",
          "count": 5570
        },
        {
          "label": "hair-dryer",
          "title": "Hair dryer",
          "count": 4732
        },
        {
          "label": "pets-allowed",
          "title": "Pets allowed",
          "count": 4430
        },
        {
          "label": "breakfast",
          "title": "Breakfast",
          "count": 3919
        },
        {
          "label": "washer",
          "title": "Washer",
          "count": 3469
        },
        {
          "label": "luggage-dropoff-allowed",
          "title": "Luggage dropoff allowed",
          "count": 3399
        },
        {
          "label": "air-conditioning",
          "title": "Air conditioning",
          "count": 3171
        },
        {
          "label": "24-hour-check-in",
          "title": "24-hour check-in",
          "count": 3066
        },
        {
          "label": "desk",
          "title": "Desk",
          "count": 3027
        },
        {
          "label": "gym",
          "title": "GYM",
          "count": 3005
        },
        {
          "label": "coffee-maker",
          "title": "Coffee maker",
          "count": 2525
        },
        {
          "label": "iron",
          "title": "Iron",
          "count": 2436
        },
        {
          "label": "kitchen",
          "title": "Kitchen",
          "count": 2382
        },
        {
          "label": "handheld-shower-head",
          "title": "Handheld shower head",
          "count": 2029
        },
        {
          "label": "essentials",
          "title": "Essentials",
          "count": 2026
        },
        {
          "label": "safe",
          "title": "Safe",
          "count": 1966
        },
        {
          "label": "mini-fridge",
          "title": "Mini fridge",
          "count": 1957
        },
        {
          "label": "elevator",
          "title": "Elevator",
          "count": 1890
        },
        {
          "label": "free-parking-in-premises",
          "title": "Free parking",
          "count": 1881
        },
        {
          "label": "service-staff",
          "title": "Service staff",
          "count": 1706
        },
        {
          "label": "other",
          "title": "Other",
          "count": 1685
        },
        {
          "label": "wheelchair-accessible",
          "title": "Wheelchair accessible",
          "count": 1685
        },
        {
          "label": "patio-or-balcony",
          "title": "Patio or balcony",
          "count": 1523
        },
        {
          "label": "sauna",
          "title": "Sauna",
          "count": 1392
        },
        {
          "label": "swimming-pool",
          "title": "Swimming pool",
          "count": 1264
        },
        {
          "label": "paid-parking-on-premises",
          "title": "Paid parking on premises",
          "count": 1194
        },
        {
          "label": "sofabed",
          "title": "Sofa bed",
          "count": 1067
        },
        {
          "label": "children-allowed",
          "title": "Children allowed",
          "count": 991
        },
        {
          "label": "smart-tv",
          "title": "Smart TV",
          "count": 938
        },
        {
          "label": "kitchenette",
          "title": "Kitchenette",
          "count": 863
        },
        {
          "label": "spa",
          "title": "Spa",
          "count": 843
        },
        {
          "label": "bathtub",
          "title": "Bathtub",
          "count": 836
        },
        {
          "label": "bathrobe",
          "title": "Bathrobe",
          "count": 799
        },
        {
          "label": "city-view",
          "title": "City view",
          "count": 799
        },
        {
          "label": "heating",
          "title": "Heating",
          "count": 717
        },
        {
          "label": "balcony",
          "title": "Balcony",
          "count": 588
        },
        {
          "label": "microwave",
          "title": "Microwave",
          "count": 554
        },
        {
          "label": "paid-parking-off-premises",
          "title": "Paid parking off premises",
          "count": 476
        },
        {
          "label": "ev-charger",
          "title": "EV charger",
          "count": 456
        },
        {
          "label": "refrigerator",
          "title": "Refrigerator",
          "count": 428
        },
        {
          "label": "sea-view",
          "title": "Sea view",
          "count": 402
        },
        {
          "label": "kettle",
          "title": "Kettle",
          "count": 398
        },
        {
          "label": "outdoor-pool",
          "title": "Outdoor pool",
          "count": 394
        },
        {
          "label": "additional-amenities",
          "title": "Additional amenities",
          "count": 347
        },
        {
          "label": "hot-tub",
          "title": "Hot tub",
          "count": 331
        },
        {
          "label": "dishwasher",
          "title": "Dishwasher",
          "count": 318
        },
        {
          "label": "toaster",
          "title": "Toaster",
          "count": 315
        },
        {
          "label": "oven",
          "title": "Oven",
          "count": 226
        },
        {
          "label": "bbq-grill",
          "title": "BBQ grill",
          "count": 215
        },
        {
          "label": "garden",
          "title": "Garden",
          "count": 200
        },
        {
          "label": "near-ocean",
          "title": "Near ocean",
          "count": 197
        },
        {
          "label": "indoor-pool",
          "title": "Indoor pool",
          "count": 182
        },
        {
          "label": "rain-shower",
          "title": "Rain shower",
          "count": 177
        },
        {
          "label": "self-check-in",
          "title": "Self check-in",
          "count": 166
        },
        {
          "label": "mountain-view",
          "title": "Mountain view",
          "count": 142
        },
        {
          "label": "beach",
          "title": "Beach",
          "count": 131
        },
        {
          "label": "stove",
          "title": "Stove",
          "count": 118
        },
        {
          "label": "dvd-player",
          "title": "DVD player",
          "count": 107
        },
        {
          "label": "river",
          "title": "River",
          "count": 107
        },
        {
          "label": "rooftop-pool",
          "title": "Rooftop pool",
          "count": 99
        },
        {
          "label": "garage",
          "title": "Garage",
          "count": 96
        },
        {
          "label": "cable-tv",
          "title": "Cable TV",
          "count": 92
        },
        {
          "label": "smoke-detector",
          "title": "Smoke detector",
          "count": 83
        },
        {
          "label": "garden-or-backyard",
          "title": "Garden or backyard",
          "count": 64
        },
        {
          "label": "towels",
          "title": "Towels",
          "count": 57
        },
        {
          "label": "shared-kitchen",
          "title": "Shared kitchen",
          "count": 49
        },
        {
          "label": "ocean-front",
          "title": "Ocean front",
          "count": 46
        },
        {
          "label": "stereo-system",
          "title": "Stereo system",
          "count": 45
        },
        {
          "label": "bunk-bed",
          "title": "Bunk bed",
          "count": 44
        },
        {
          "label": "beach-essentials",
          "title": "Beach essentials",
          "count": 40
        },
        {
          "label": "fireplace",
          "title": "Fireplace",
          "count": 35
        },
        {
          "label": "beach-front",
          "title": "Beach front",
          "count": 27
        },
        {
          "label": "garden-view",
          "title": "Garden view",
          "count": 20
        },
        {
          "label": "beach-view",
          "title": "Beach view",
          "count": 19
        },
        {
          "label": "ski-in",
          "title": "Ski in",
          "count": 14
        },
        {
          "label": "lake-front",
          "title": "Lake front",
          "count": 13
        },
        {
          "label": "golf-view",
          "title": "Golf view",
          "count": 10
        },
        {
          "label": "formal-dining-area",
          "title": "Formal dining area",
          "count": 7
        },
        {
          "label": "private-entrance",
          "title": "Private entrance",
          "count": 7
        },
        {
          "label": "toiletries",
          "title": "Toiletries",
          "count": 7
        },
        {
          "label": "water-view",
          "title": "Water view",
          "count": 6
        },
        {
          "label": "private-bathroom",
          "title": "Private bathroom",
          "count": 4
        },
        {
          "label": "step-free-shower",
          "title": "Step-free shower",
          "count": 3
        },
        {
          "label": "en-suite-bathroom",
          "title": "En suite bathroom",
          "count": 1
        },
        {
          "label": "first-aid-kit",
          "title": "First aid kit",
          "count": 1
        }
      ]
    },
    "house_rules": {
      "labels": [
        "children-allowed",
        "infants-allowed",
        "pets-are-welcome",
        "smoking-allowed",
        "events-allowed"
      ],
      "in_use": [
        {
          "label": "pets-are-welcome",
          "count": 4447
        },
        {
          "label": "children-allowed",
          "count": 1330
        },
        {
          "label": "infants-allowed",
          "count": 941
        }
      ],
      "defaults": "none written: Maveriks shows its own defaults where an operator states nothing"
    },
    "sort": {
      "price": "base price, lowest first",
      "-rating": "rating, highest first"
    },
    "listing_record_example": {
      "unit": {
        "api_id": "972520",
        "pms": "The Nauru Apartments",
        "title": "Balluta Bay Reef Villa",
        "nickname": "balluta-bay-reef-villa",
        "mtl_type": "single",
        "filters": {
          "city": "Malta",
          "location": {
            "latitude": 35.89594,
            "longitude": 14.52943
          },
          "bedrooms": 3,
          "bathrooms": 2,
          "accommodates": 6,
          "property_type": "villa",
          "amenities": [
            "air-conditioning",
            "bbq-grill",
            "beach-essentials",
            "beach-front",
            "children-allowed",
            "coffee-maker",
            "essentials",
            "kitchen",
            "ocean-front",
            "swimming-pool",
            "tv",
            "washer",
            "wireless-internet"
          ]
        }
      },
      "listing": {
        "deal_type": "short-term",
        "currency": "USD",
        "isPublished": true,
        "isTitleManualByLocale": {
          "en": false
        },
        "isDescriptionManualByLocale": {
          "en": false
        },
        "description": {
          "summary": "A villa steps from the sand on Balluta Bay beach. Sleeps up to 6 guests across 3 bedrooms with a private pool. Fully self-contained with Wi-Fi, air conditioning and a full kitchen."
        },
        "metadata": {
          "sharingPrivacyStatus": "Public",
          "calendarAvailabilityWindow": 274
        },
        "attributes": {
          "base_price": {
            "amount": 485,
            "currency": "USD"
          },
          "stay_nights_range": {
            "min": 1,
            "max": 274
          },
          "house_rules": [
            "children-allowed"
          ],
          "rating": 4.3,
          "rating_count": 105
        }
      },
      "operator_facts": {
        "country_code": "MT",
        "property_type_as_published": "villa"
      },
      "price_source": "rate_card"
    },
    "mapping_document": "docs/MAVERIKS-MAPPING.md in the repository: each operator's API field → this field → the Maveriks field"
  },
  "self_url": "/ai/v1/schema",
  "terms_url": "/terms",
  "terms_notice": "Use of this API is subject to its terms, which may be updated — the current version at /terms governs use.",
  "search_url": "/ai/v1/search"
}