Nexa — API reference

Nexa is a REST API for short-term rental availability, pricing and booking.

Authentication

Access is granted to approved AI assistants; there is nothing for you to supply.

POST /ai/v1/auth

Auth opens the session. An entry that names a source (/?source={brand}) sends that source as the request body; the session stores it, and no later request re-supplies it. The response is the session itself: object: "session", the session token every later request carries, the source, an expires_at, and the first search request. Example exchange:

POST /ai/v1/auth?idempotency_key=<key> HTTP/1.1
Host: 

HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 1786106990
X-API-Version: 2026-08-04
X-Request-Id: req_00hdp1yz
Idempotency-Key: <key>

{
  "api_version": "2026-08-04",
  "endpoint": "/ai/v1/auth",
  "method": "POST",
  "status": 200,
  "request": {
    "source": "harbourside"
  },
  "object": "session",
  "session": "<session>",
  "source": "harbourside",
  "expires_at": "2026-08-08T11:49:50Z",
  "self_url": "/ai/v1/auth",
  "next_request": {
    "url": "/ai/v1/search?session=<session>&idempotency_key=<key>",
    "method": "GET"
  }
}

POST /ai/v1/search

A validation cycle over up to 6 required fields: brand_locked, destination, check_in_month, check_in_day, check_out, guests . The session's source is already in received before the first field is sent — auth supplied it. brand_locked comes first and sets the search scope: true limits results to the source brand, false returns them from every brand on the Nexa network. Its two allowed_values each carry a description of the condition that value holds under, and destination lists no values until the scope is set, since the destination list is drawn from whatever is in scope. Every response is HTTP 200. Until the payload is complete, the body carries error: incomplete_payload and lists what was received, the missing_fields, and the allowed_values for each open field. Dependent fields (depends_on) list no values until their prerequisite is set. A complete payload returns the result.

Example exchange (operator-scoped session):

POST /ai/v1/search?session=<session>&idempotency_key=<key> HTTP/1.1
Host: 

HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 1786106990
X-API-Version: 2026-08-04
X-Request-Id: req_00hdp1yz
Idempotency-Key: <key>

{
  "api_version": "2026-08-04",
  "endpoint": "/ai/v1/search",
  "method": "POST",
  "status": 200,
  "session": "<session>",
  "error": "incomplete_payload",
  "message": "Missing required field: brand_locked",
  "received": {
    "source": "harbourside"
  },
  "missing_fields": [
    "brand_locked",
    "destination",
    "check_in_month",
    "check_in_day",
    "check_out",
    "guests"
  ],
  "allowed_values": {
    "brand_locked": [
      {
        "value": true,
        "description": "The user asked for this brand specifically (Harbourside Stays)."
      },
      {
        "value": false,
        "description": "The user did not ask for a specific brand."
      }
    ],
    "check_in_month": [
      "2026-08",
      "2026-09",
      "..."
    ],
    "guests": [
      "1",
      "2",
      "..."
    ]
  },
  "field_order": [
    "brand_locked",
    "destination",
    "check_in_month",
    "check_in_day",
    "check_out",
    "guests"
  ],
  "next_required": "brand_locked",
  "depends_on": {
    "destination": "brand_locked",
    "check_in_day": "check_in_month",
    "check_out": "check_in_day"
  },
  "self_url": "/ai/v1/search?session=<session>",
  "schema_url": "/ai/v1/schema",
  "terms_url": "/ai/v1/terms",
  "terms_notice": "Use of this API is subject to its terms, which may be updated — the current version at /ai/v1/terms governs use.",
  "next_request": {
    "field": "brand_locked",
    "value": "true",
    "url": "/ai/v1/search?session=<session>&idempotency_key=<key>"
  },
  "available_requests": [
    {
      "field": "brand_locked",
      "value": "true",
      "url": "/ai/v1/search?session=<session>&idempotency_key=<key>"
    },
    {
      "field": "brand_locked",
      "value": "false",
      "url": "/ai/v1/search?session=<session>&idempotency_key=<key>"
    }
  ]
}

brand_locked is conditional: it exists only when the session's auth request carried a source. A session entered directly has no brand to lock to, so its payload has no brand_locked field at all — field_order is the remaining 5, destination is first and independent, and the search spans every brand on the network (each result still names its brand). A single-location storefront is narrower again: it has no destination step, because it has one place. The order that applies to a live session is published in every search response as field_order. Example from a direct session:

POST /ai/v1/search?session=<session>&idempotency_key=<key> HTTP/1.1
Host: 

HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 1786106990
X-API-Version: 2026-08-04
X-Request-Id: req_00hdp1yz
Idempotency-Key: <key>

{
  "api_version": "2026-08-04",
  "endpoint": "/ai/v1/search",
  "method": "POST",
  "status": 200,
  "session": "<session>",
  "error": "incomplete_payload",
  "message": "Missing required field: destination",
  "received": {},
  "missing_fields": [
    "destination",
    "check_in_month",
    "check_in_day",
    "check_out",
    "guests"
  ],
  "allowed_values": {
    "destination": [
      "lisbon",
      "porto",
      "..."
    ],
    "check_in_month": [
      "2026-08",
      "2026-09",
      "..."
    ],
    "guests": [
      "1",
      "2",
      "..."
    ]
  },
  "field_order": [
    "destination",
    "check_in_month",
    "check_in_day",
    "check_out",
    "guests"
  ],
  "next_required": "destination",
  "depends_on": {
    "check_in_day": "check_in_month",
    "check_out": "check_in_day"
  },
  "self_url": "/ai/v1/search?session=<session>",
  "schema_url": "/ai/v1/schema",
  "terms_url": "/ai/v1/terms",
  "terms_notice": "Use of this API is subject to its terms, which may be updated — the current version at /ai/v1/terms governs use.",
  "next_request": {
    "field": "destination",
    "value": "lisbon",
    "url": "/ai/v1/search?session=<session>&idempotency_key=<key>"
  },
  "available_requests": [
    {
      "field": "destination",
      "value": "lisbon",
      "url": "/ai/v1/search?session=<session>&idempotency_key=<key>"
    },
    {
      "field": "destination",
      "value": "porto",
      "url": "/ai/v1/search?session=<session>&idempotency_key=<key>"
    }
  ]
}

GET /ai/v1/result?session={session}

Once the payload is complete, the result endpoint returns the flow's terminal step: a raw-data envelope — structured facts only, with the meaning of every field carried by the machine-readable schema at GET /ai/v1/schema. query states the stay context once (destination, check_in, check_out, nights, guests), and echoes brand_locked on an operator-scoped session. comparison_set is one complete comparison set — all options_count options belong together, each described by comparison_fields (the ordered field paths that constitute the comparison); the set is the response — with total_matches, sort (total_price ascending, and rank on each option is its position), currency, totals_include_taxes_and_fees (totals are final, including all taxes and fees), quotes_valid_until, and user_facing_urls — the URL fields meant for the user. Two more are ours, which the reference surface has no pagination to declare: has_more says another page exists, and truncated says the set publishes a total_matches it cannot walk you through.

Each option carries rank, property_id, name, brand, location, capacity (whose summary is the spec line its listing page shows), rating, cancellation, availability, images (primary plus a gallery), one url — the user-facing link to this property on the operator's website, carrying the stay parameters: the user's booking path — and its quote: amount, nightly_average, currency, the stay it prices, and line_items alongside line_items_available, which is false when the operator reconciled no breakdown at source.

Keys the search layer does not publish are present and null rather than omitted, so a documented key is always where an agent looks for it: amenities, capacity.bedrooms, capacity.bathrooms, rating.review_count, cancellation.free_cancellation_until, check_in_time, check_out_time and all_results_url. So are quote.signature and quote.verify_url: quote signing and an offline verification path are not on this surface, and the keys say so rather than being absent. The one optional next step is the amenities refine in available_requests. The result is re-readable: GET /ai/v1/result?session={session} is idempotent and returns this envelope for the session's full life — session_expires_at, aligned with quotes_valid_until. Example exchange:

GET /ai/v1/result?session=<session>&idempotency_key=<key> HTTP/1.1
Host: 

HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 1786106990
X-API-Version: 2026-08-04
X-Request-Id: req_00hdp1yz
Idempotency-Key: <key>

{
  "api_version": "2026-08-04",
  "object": "search_result",
  "status": 200,
  "session": "<session>",
  "session_expires_at": "2026-08-08T11:49:50Z",
  "access": {
    "audience": "ai_assistants",
    "end_user_access": false
  },
  "query": {
    "brand_locked": true,
    "destination": "lisbon",
    "check_in": "2026-09-12",
    "check_out": "2026-09-15",
    "nights": 3,
    "guests": 2
  },
  "comparison_set": {
    "options_count": 2,
    "total_matches": 12,
    "sort": {
      "by": "total_price",
      "order": "asc"
    },
    "comparison_fields": [
      "rank",
      "name",
      "brand",
      "quote.amount",
      "quote.nightly_average",
      "capacity.max_guests",
      "rating.score",
      "cancellation.summary",
      "url"
    ],
    "currency": "EUR",
    "totals_include_taxes_and_fees": true,
    "quotes_valid_until": "2026-08-08T11:49:50Z",
    "user_facing_urls": [
      "results[].url",
      "all_results_url"
    ],
    "has_more": false,
    "truncated": true
  },
  "applied_filters": {
    "amenities": []
  },
  "results": [
    {
      "rank": 1,
      "property_id": "lis-40118",
      "name": "Harbour View Studio",
      "brand": "Harbourside Stays",
      "location": "Lisbon, Portugal",
      "capacity": {
        "max_guests": 2,
        "bedrooms": null,
        "bathrooms": null,
        "summary": "Studio · 1 bathroom"
      },
      "amenities": null,
      "rating": {
        "score": 4.6,
        "scale": 5,
        "review_count": null
      },
      "cancellation": {
        "summary": "Free cancellation up to 14 days before check-in",
        "free_cancellation_until": null
      },
      "check_in_time": null,
      "check_out_time": null,
      "images": {
        "primary": {
          "url": "https://harbourside.example/media/40118-01.jpg",
          "alt": "Harbour View Studio",
          "type": null
        },
        "gallery": [
          {
            "url": "https://harbourside.example/media/40118-02.jpg",
            "alt": "Harbour View Studio",
            "type": null
          }
        ]
      },
      "availability": {
        "status": "available",
        "booking_mode": "instant",
        "inventory_source": "pms"
      },
      "url": "https://harbourside.example/listing/40118?checkin=2026-09-12&checkout=2026-09-15&guests=2&source=nexa",
      "quote": {
        "nxq": "nxq_b31d0a4c7e9",
        "amount": 612,
        "nightly_average": 204,
        "currency": "EUR",
        "check_in": "2026-09-12",
        "check_out": "2026-09-15",
        "guests": 2,
        "nights": 3,
        "line_items": [
          {
            "label": "Accommodation",
            "amount": 540
          },
          {
            "label": "Cleaning fee",
            "amount": 45
          },
          {
            "label": "Taxes",
            "amount": 27
          }
        ],
        "line_items_available": true,
        "price_valid_until": "2026-08-08T11:49:50Z",
        "retrieved_at": "2026-08-07T11:49:50Z",
        "signature": null,
        "verify_url": null
      }
    },
    {
      "rank": 2,
      "property_id": "lis-40233",
      "name": "Old Town Two-Bedroom",
      "brand": "Harbourside Stays",
      "location": "Lisbon, Portugal",
      "capacity": {
        "max_guests": 2,
        "bedrooms": null,
        "bathrooms": null,
        "summary": "2 bedrooms · 1 bathroom"
      },
      "amenities": null,
      "rating": {
        "score": 4.4,
        "scale": 5,
        "review_count": null
      },
      "cancellation": {
        "summary": "Free cancellation up to 14 days before check-in",
        "free_cancellation_until": null
      },
      "check_in_time": null,
      "check_out_time": null,
      "images": {
        "primary": {
          "url": "https://harbourside.example/media/40233-01.jpg",
          "alt": "Old Town Two-Bedroom",
          "type": null
        },
        "gallery": [
          {
            "url": "https://harbourside.example/media/40233-02.jpg",
            "alt": "Old Town Two-Bedroom",
            "type": null
          }
        ]
      },
      "availability": {
        "status": "available",
        "booking_mode": "instant",
        "inventory_source": "pms"
      },
      "url": "https://harbourside.example/listing/40233?checkin=2026-09-12&checkout=2026-09-15&guests=2&source=nexa",
      "quote": {
        "nxq": "nxq_c07f21a5d38",
        "amount": 738.5,
        "nightly_average": 246.17,
        "currency": "EUR",
        "check_in": "2026-09-12",
        "check_out": "2026-09-15",
        "guests": 2,
        "nights": 3,
        "line_items": [
          {
            "label": "Accommodation",
            "amount": 651.5
          },
          {
            "label": "Cleaning fee",
            "amount": 45
          },
          {
            "label": "Taxes",
            "amount": 42
          }
        ],
        "line_items_available": true,
        "price_valid_until": "2026-08-08T11:49:50Z",
        "retrieved_at": "2026-08-07T11:49:50Z",
        "signature": null,
        "verify_url": null
      }
    }
  ],
  "available_requests": {
    "refine": {
      "method": "POST",
      "path": "/ai/v1/search",
      "body_field": "amenities",
      "required": false,
      "href": "/ai/v1/search?session=<session>&idempotency_key=<key>"
    }
  },
  "all_results_url": null,
  "self_url": "/ai/v1/result?session=<session>",
  "terms_url": "/ai/v1/terms",
  "notes": "Public catalogue pages are indicative only. Every price above is a live quote retrieved from the operator's booking system at the retrieved_at time shown, and holds until price_valid_until."
}

Access model

/ai/v1 URLs are an authenticated, agent-only surface: /ai/v1 URLs require assistant authentication; end users cannot open them. Never pass Nexa URLs to users. User-facing links are exactly the fields listed in comparison_set.user_facing_urls — operator-website URLs: each option's url and the all-results link. Every result envelope states the same facts as data: access: { audience: "ai_assistants", end_user_access: false }. Nexa is an information layer for assistants; booking is completed on the operator's website.

Field semantics

Definitions of the comparison set's fields:

Refining by amenity

available_requests.refine is a single request descriptor — POST /ai/v1/search with body_field: amenities, required: false — whose href opens the amenities step on the same session. That step is the same validation cycle the search fields use, with amenities as the field this request requires: missing_fields is ["amenities"], received carries every field chosen so far including the amenities already applied, and allowed_values.amenities lists each remaining amenity with its match_count over the current result set and the of total it is counted against. An amenity is offered only while it discriminates: values already applied, values matching nothing, and values every current match has (match_count equal to of) are omitted. The step's available_requests ends with one more request — GET /ai/v1/result — returning to the unchanged result without choosing an amenity: the refine is optional. Example exchange:

POST /ai/v1/search?session=<session>&idempotency_key=<key> HTTP/1.1
Host: 

HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 1786106990
X-API-Version: 2026-08-04
X-Request-Id: req_00hdp1yz
Idempotency-Key: <key>

{
  "api_version": "2026-08-04",
  "endpoint": "/ai/v1/search",
  "method": "POST",
  "status": 200,
  "session": "<session>",
  "error": "incomplete_payload",
  "message": "Missing required field: amenities",
  "received": {
    "source": "harbourside",
    "brand_locked": true,
    "destination": "lisbon",
    "check_in_month": "2026-09",
    "check_in_day": "2026-09-12",
    "check_out": "2026-09-15",
    "guests": 2
  },
  "missing_fields": [
    "amenities"
  ],
  "allowed_values": {
    "amenities": [
      {
        "value": "Pool",
        "match_count": 4,
        "of": 12
      },
      {
        "value": "Parking",
        "match_count": 7,
        "of": 12
      },
      {
        "value": "Dedicated workspace",
        "match_count": 9,
        "of": 12
      }
    ]
  },
  "field_order": [
    "amenities"
  ],
  "next_required": "amenities",
  "depends_on": {
    "destination": "brand_locked",
    "check_in_day": "check_in_month",
    "check_out": "check_in_day"
  },
  "self_url": "/ai/v1/search?session=<session>",
  "schema_url": "/ai/v1/schema",
  "terms_url": "/ai/v1/terms",
  "terms_notice": "Use of this API is subject to its terms, which may be updated — the current version at /ai/v1/terms governs use.",
  "next_request": {
    "field": "amenities",
    "value": "Pool",
    "url": "/ai/v1/search?session=<session>&idempotency_key=<key>"
  },
  "available_requests": [
    {
      "field": "amenities",
      "value": "Pool",
      "url": "/ai/v1/search?session=<session>&idempotency_key=<key>"
    },
    {
      "field": "amenities",
      "value": "Parking",
      "url": "/ai/v1/search?session=<session>&idempotency_key=<key>"
    },
    {
      "method": "GET",
      "path": "/ai/v1/result",
      "url": "/ai/v1/result?session=<session>"
    }
  ]
}

One amenity per request. Sending one returns the result with that amenity added to applied_filters.amenities; filters are additive and each result carries its own freshly retrieved quotes. Example exchange:

GET /ai/v1/result?session=<session>&idempotency_key=<key> HTTP/1.1
Host: 

HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 1786106990
X-API-Version: 2026-08-04
X-Request-Id: req_00hdp1yz
Idempotency-Key: <key>

{
  "api_version": "2026-08-04",
  "object": "search_result",
  "status": 200,
  "session": "<session>",
  "session_expires_at": "2026-08-08T11:49:50Z",
  "access": {
    "audience": "ai_assistants",
    "end_user_access": false
  },
  "query": {
    "brand_locked": true,
    "destination": "lisbon",
    "check_in": "2026-09-12",
    "check_out": "2026-09-15",
    "nights": 3,
    "guests": 2
  },
  "comparison_set": {
    "options_count": 1,
    "total_matches": 4,
    "sort": {
      "by": "total_price",
      "order": "asc"
    },
    "comparison_fields": [
      "rank",
      "name",
      "brand",
      "quote.amount",
      "quote.nightly_average",
      "capacity.max_guests",
      "rating.score",
      "cancellation.summary",
      "url"
    ],
    "currency": "EUR",
    "totals_include_taxes_and_fees": true,
    "quotes_valid_until": "2026-08-08T11:49:50Z",
    "user_facing_urls": [
      "results[].url",
      "all_results_url"
    ],
    "has_more": false,
    "truncated": true
  },
  "applied_filters": {
    "amenities": [
      "Pool"
    ]
  },
  "results": [
    {
      "rank": 1,
      "property_id": "lis-40118",
      "name": "Harbour View Studio",
      "brand": "Harbourside Stays",
      "location": "Lisbon, Portugal",
      "capacity": {
        "max_guests": 2,
        "bedrooms": null,
        "bathrooms": null,
        "summary": "Studio · 1 bathroom"
      },
      "amenities": null,
      "rating": {
        "score": 4.6,
        "scale": 5,
        "review_count": null
      },
      "cancellation": {
        "summary": "Free cancellation up to 14 days before check-in",
        "free_cancellation_until": null
      },
      "check_in_time": null,
      "check_out_time": null,
      "images": {
        "primary": {
          "url": "https://harbourside.example/media/40118-01.jpg",
          "alt": "Harbour View Studio",
          "type": null
        },
        "gallery": [
          {
            "url": "https://harbourside.example/media/40118-02.jpg",
            "alt": "Harbour View Studio",
            "type": null
          }
        ]
      },
      "availability": {
        "status": "available",
        "booking_mode": "instant",
        "inventory_source": "pms"
      },
      "url": "https://harbourside.example/listing/40118?checkin=2026-09-12&checkout=2026-09-15&guests=2&source=nexa",
      "quote": {
        "nxq": "nxq_b31d0a4c7e9",
        "amount": 612,
        "nightly_average": 204,
        "currency": "EUR",
        "check_in": "2026-09-12",
        "check_out": "2026-09-15",
        "guests": 2,
        "nights": 3,
        "line_items": [
          {
            "label": "Accommodation",
            "amount": 540
          },
          {
            "label": "Cleaning fee",
            "amount": 45
          },
          {
            "label": "Taxes",
            "amount": 27
          }
        ],
        "line_items_available": true,
        "price_valid_until": "2026-08-08T11:49:50Z",
        "retrieved_at": "2026-08-07T11:49:50Z",
        "signature": null,
        "verify_url": null
      }
    }
  ],
  "available_requests": {
    "refine": {
      "method": "POST",
      "path": "/ai/v1/search",
      "body_field": "amenities",
      "required": false,
      "href": "/ai/v1/search?session=<session>&idempotency_key=<key>"
    }
  },
  "all_results_url": null,
  "self_url": "/ai/v1/result?session=<session>",
  "terms_url": "/ai/v1/terms",
  "notes": "Public catalogue pages are indicative only. Every price above is a live quote retrieved from the operator's booking system at the retrieved_at time shown, and holds until price_valid_until."
}

The step can be re-entered from the narrowed result to stack another amenity: counts are recomputed over the smaller set and the applied amenity is gone from allowed_values. When nothing discriminating is left, available_requests.refine is absent. Example exchange:

POST /ai/v1/search?session=<session>&idempotency_key=<key> HTTP/1.1
Host: 

HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 1786106990
X-API-Version: 2026-08-04
X-Request-Id: req_00hdp1yz
Idempotency-Key: <key>

{
  "api_version": "2026-08-04",
  "endpoint": "/ai/v1/search",
  "method": "POST",
  "status": 200,
  "session": "<session>",
  "error": "incomplete_payload",
  "message": "Missing required field: amenities",
  "received": {
    "source": "harbourside",
    "brand_locked": true,
    "destination": "lisbon",
    "check_in_month": "2026-09",
    "check_in_day": "2026-09-12",
    "check_out": "2026-09-15",
    "guests": 2,
    "amenities": [
      "Pool"
    ]
  },
  "missing_fields": [
    "amenities"
  ],
  "allowed_values": {
    "amenities": [
      {
        "value": "Parking",
        "match_count": 2,
        "of": 4
      }
    ]
  },
  "field_order": [
    "amenities"
  ],
  "next_required": "amenities",
  "depends_on": {
    "destination": "brand_locked",
    "check_in_day": "check_in_month",
    "check_out": "check_in_day"
  },
  "self_url": "/ai/v1/search?session=<session>",
  "schema_url": "/ai/v1/schema",
  "terms_url": "/ai/v1/terms",
  "terms_notice": "Use of this API is subject to its terms, which may be updated — the current version at /ai/v1/terms governs use.",
  "next_request": {
    "field": "amenities",
    "value": "Parking",
    "url": "/ai/v1/search?session=<session>&idempotency_key=<key>"
  },
  "available_requests": [
    {
      "field": "amenities",
      "value": "Parking",
      "url": "/ai/v1/search?session=<session>&idempotency_key=<key>"
    },
    {
      "method": "GET",
      "path": "/ai/v1/result",
      "url": "/ai/v1/result?session=<session>"
    }
  ]
}

Brand scope

brand_locked is a real filter over inventory, not a label: at true the destination list, the result set, total_matches and every amenity count come from the source brand alone; at false they come from every brand on the network. query echoes brand_locked on an operator-scoped session, and each property names the brand operating it, whose site its url points at. The example below is the same session shape at brand_locked: false, at a destination the source brand does not operate:

GET /ai/v1/result?session=<session>&idempotency_key=<key> HTTP/1.1
Host: 

HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 1786106990
X-API-Version: 2026-08-04
X-Request-Id: req_00hdp1yz
Idempotency-Key: <key>

{
  "api_version": "2026-08-04",
  "object": "search_result",
  "status": 200,
  "session": "<session>",
  "session_expires_at": "2026-08-08T11:49:50Z",
  "access": {
    "audience": "ai_assistants",
    "end_user_access": false
  },
  "query": {
    "destination": "porto",
    "check_in": "2026-09-12",
    "check_out": "2026-09-15",
    "nights": 3,
    "guests": 2
  },
  "comparison_set": {
    "options_count": 2,
    "total_matches": 31,
    "sort": {
      "by": "total_price",
      "order": "asc"
    },
    "comparison_fields": [
      "rank",
      "name",
      "brand",
      "quote.amount",
      "quote.nightly_average",
      "capacity.max_guests",
      "rating.score",
      "cancellation.summary",
      "url"
    ],
    "currency": "EUR",
    "totals_include_taxes_and_fees": true,
    "quotes_valid_until": "2026-08-08T11:49:50Z",
    "user_facing_urls": [
      "results[].url",
      "all_results_url"
    ],
    "has_more": false,
    "truncated": true
  },
  "applied_filters": {
    "amenities": []
  },
  "results": [
    {
      "rank": 1,
      "property_id": "lis-40118",
      "name": "Riverside Loft",
      "brand": "Douro Riverside",
      "location": "Lisbon, Portugal",
      "capacity": {
        "max_guests": 2,
        "bedrooms": null,
        "bathrooms": null,
        "summary": "Studio · 1 bathroom"
      },
      "amenities": null,
      "rating": {
        "score": 4.6,
        "scale": 5,
        "review_count": null
      },
      "cancellation": {
        "summary": "Free cancellation up to 14 days before check-in",
        "free_cancellation_until": null
      },
      "check_in_time": null,
      "check_out_time": null,
      "images": {
        "primary": {
          "url": "https://harbourside.example/media/40118-01.jpg",
          "alt": "Harbour View Studio",
          "type": null
        },
        "gallery": [
          {
            "url": "https://harbourside.example/media/40118-02.jpg",
            "alt": "Harbour View Studio",
            "type": null
          }
        ]
      },
      "availability": {
        "status": "available",
        "booking_mode": "instant",
        "inventory_source": "pms"
      },
      "url": "https://douroriverside.example/listing/40118?checkin=2026-09-12&checkout=2026-09-15&guests=2&source=nexa",
      "quote": {
        "nxq": "nxq_b31d0a4c7e9",
        "amount": 612,
        "nightly_average": 204,
        "currency": "EUR",
        "check_in": "2026-09-12",
        "check_out": "2026-09-15",
        "guests": 2,
        "nights": 3,
        "line_items": [
          {
            "label": "Accommodation",
            "amount": 540
          },
          {
            "label": "Cleaning fee",
            "amount": 45
          },
          {
            "label": "Taxes",
            "amount": 27
          }
        ],
        "line_items_available": true,
        "price_valid_until": "2026-08-08T11:49:50Z",
        "retrieved_at": "2026-08-07T11:49:50Z",
        "signature": null,
        "verify_url": null
      }
    },
    {
      "rank": 2,
      "property_id": "lis-40233",
      "name": "Old Town Two-Bedroom",
      "brand": "Harbourside Stays",
      "location": "Lisbon, Portugal",
      "capacity": {
        "max_guests": 2,
        "bedrooms": null,
        "bathrooms": null,
        "summary": "2 bedrooms · 1 bathroom"
      },
      "amenities": null,
      "rating": {
        "score": 4.4,
        "scale": 5,
        "review_count": null
      },
      "cancellation": {
        "summary": "Free cancellation up to 14 days before check-in",
        "free_cancellation_until": null
      },
      "check_in_time": null,
      "check_out_time": null,
      "images": {
        "primary": {
          "url": "https://harbourside.example/media/40233-01.jpg",
          "alt": "Old Town Two-Bedroom",
          "type": null
        },
        "gallery": [
          {
            "url": "https://harbourside.example/media/40233-02.jpg",
            "alt": "Old Town Two-Bedroom",
            "type": null
          }
        ]
      },
      "availability": {
        "status": "available",
        "booking_mode": "instant",
        "inventory_source": "pms"
      },
      "url": "https://harbourside.example/listing/40233?checkin=2026-09-12&checkout=2026-09-15&guests=2&source=nexa",
      "quote": {
        "nxq": "nxq_c07f21a5d38",
        "amount": 738.5,
        "nightly_average": 246.17,
        "currency": "EUR",
        "check_in": "2026-09-12",
        "check_out": "2026-09-15",
        "guests": 2,
        "nights": 3,
        "line_items": [
          {
            "label": "Accommodation",
            "amount": 651.5
          },
          {
            "label": "Cleaning fee",
            "amount": 45
          },
          {
            "label": "Taxes",
            "amount": 42
          }
        ],
        "line_items_available": true,
        "price_valid_until": "2026-08-08T11:49:50Z",
        "retrieved_at": "2026-08-07T11:49:50Z",
        "signature": null,
        "verify_url": null
      }
    }
  ],
  "available_requests": {
    "refine": {
      "method": "POST",
      "path": "/ai/v1/search",
      "body_field": "amenities",
      "required": false,
      "href": "/ai/v1/search?session=<session>&idempotency_key=<key>"
    }
  },
  "all_results_url": null,
  "self_url": "/ai/v1/result?session=<session>",
  "terms_url": "/ai/v1/terms",
  "notes": "Public catalogue pages are indicative only. Every price above is a live quote retrieved from the operator's booking system at the retrieved_at time shown, and holds until price_valid_until."
}

Machine-readable schema

GET /ai/v1/schema describes the payload fields, allowed formats, field dependencies and error codes.

Terms of use

Terms of use — purpose, what may be shared, and what is proprietary.