> ## Documentation Index
> Fetch the complete documentation index at: https://docs.d-edge.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> This site documents the D-EDGE APIs for technology partners (web agencies, OTAs, PMS and RMS vendors, booking and CRM tools). It is written for developers integrating with hotels that use the D-EDGE platform.
> Seven APIs, grouped by domain. Connectivity: Channel Manager API (distribute a hotel's inventory to your channel and send bookings back). Pricing & Availability: Best Price API (best offers to highlight), Quotation API (all bookable offers for a stay), Daily Rates API (daily availability between two dates). Booking: Booking Engine API (a hotel's full direct booking funnel), Gift Shop API (gift vouchers from catalogue to payment). Guest Management: CRM API (guest profiles).
> Pick the API by the need: to display prices, use the Best Price API or the Daily Rates API; to quote a specific stay, use the Quotation API; to book, use the Booking Engine API; to sell gift vouchers, use the Gift Shop API; to manage guest profiles, use the CRM API; to receive hotel inventory and send bookings back as a distribution channel, use the Channel Manager API.
> Base URLs, hotel identifiers, and credentials are delivered by the D-EDGE partnership team during onboarding. Do not invent base URLs, endpoints, parameters, or example credentials that do not appear on these pages.
> Authentication differs per API: OAuth 1.0 for Best Price, Quotation, Daily Rates, and Booking Engine; an X-API-KEY header for Gift Shop; OAuth 2.0 client credentials for CRM; partner-defined credentials carried in the XML message for the Channel Manager API. Read each API's Authentication page before writing code.
> A mock environment returns realistic but fictitious data without credentials. The Postman and Insomnia collections on the API collections page target it by default. Use it for exploration and tests, never for production traffic.
> Use the official names exactly: D-EDGE, D-EDGE platform, Channel Manager API, partnership team. Do not use Back Office or Extranet.
> Each API has a Changelog page. Check it for the current behaviour before relying on an older answer. For questions these pages do not answer, direct the reader to the Contact us page.

# Bookable offers showcase

> Show every available room and rate for a chosen stay

Once a guest has picked a date and a length of stay, a side-by-side view of every bookable room and rate lets them compare prices and conditions at a glance. One Stay quotation call powers the whole showcase.

## Prerequisites

Before building the showcase, make sure you have:

* A working Quotation call. If not, follow the [Quick start](/quotation-api/get-started/quick-start.md) first.
* A hotel ID and a group ID for the property you display.
* An arrival date and a night count, picked by the guest earlier in the funnel.

## How it works

The Stay quotation endpoint returns every room and rate combination available for the requested stay, grouped by rate plan, then by hotel, then by entity. Each entity carries the price, the availability count, the included articles, and any restriction or cancellation condition. You map the array of entities directly onto your comparison table: one entity, one row.

A single call covers the whole catalogue. No iteration per room, no per-rate request.

## Build the showcase

> [!NOTE]
>
> The examples below assume the OAuth signing setup from the [Quick start](/quotation-api/get-started/quick-start.md).

### 1. Request the quote for the chosen stay

Call the Stay quotation endpoint once with the arrival date, night count, and party. Use the `layout` field to ask for only the properties your table displays. The example below covers a typical comparison view: status, availability, total price, included articles, and cancellation condition.

```bash cURL theme={null}
curl --request POST \
  --url https://{apiBaseUrl}/Planning/2018A/Quotation/GetStayPlanning \
  --header 'Authorization: OAuth ...' \
  --header 'Content-Type: application/json' \
  --data '{
    "arrivalDate": "2026-12-07",
    "nightCount": 3,
    "guests": [{"adults": 2}],
    "filter": {
      "ratePlans": [{
        "groupId": <groupId>,
        "hotels": {"exceptions": [<hotelId>], "default": "Excluded"}
      }],
      "currencies": {"exceptions": ["EUR"], "default": "Excluded"}
    },
    "layout": {
      "levels": [{
        "name": "ArticleRate",
        "properties": [
          "Status",
          "Availability",
          "TotalPrice",
          "IncludedArticles",
          "CancellationCondition",
          "Occupancies"
        ]
      }]
    }
  }'
```

```javascript Node.js theme={null}
const url = "https://{apiBaseUrl}/Planning/2018A/Quotation/GetStayPlanning";

const response = await fetch(url, {
  method: "POST",
  headers: { ...authHeaders, "Content-Type": "application/json" },
  body: JSON.stringify({
    arrivalDate: "2026-12-07",
    nightCount: 3,
    guests: [{ adults: 2 }],
    filter: {
      ratePlans: [
        {
          groupId: <groupId>,
          hotels: { exceptions: [<hotelId>], default: "Excluded" },
        },
      ],
      currencies: { exceptions: ["EUR"], default: "Excluded" },
    },
    layout: {
      levels: [
        {
          name: "ArticleRate",
          properties: [
            "Status",
            "Availability",
            "TotalPrice",
            "IncludedArticles",
            "CancellationCondition",
            "Occupancies",
          ],
        },
      ],
    },
  }),
});
const data = await response.json();
```

```python Python theme={null}
response = session.post(
    "https://{apiBaseUrl}/Planning/2018A/Quotation/GetStayPlanning",
    json={
        "arrivalDate": "2026-12-07",
        "nightCount": 3,
        "guests": [{"adults": 2}],
        "filter": {
            "ratePlans": [
                {
                    "groupId": <groupId>,
                    "hotels": {"exceptions": [<hotelId>], "default": "Excluded"},
                }
            ],
            "currencies": {"exceptions": ["EUR"], "default": "Excluded"},
        },
        "layout": {
            "levels": [
                {
                    "name": "ArticleRate",
                    "properties": [
                        "Status",
                        "Availability",
                        "TotalPrice",
                        "IncludedArticles",
                        "CancellationCondition",
                        "Occupancies",
                    ],
                }
            ]
        },
    },
)
data = response.json()
```

```php PHP theme={null}
$response = $client->post('Planning/2018A/Quotation/GetStayPlanning', [
    'json' => [
        'arrivalDate' => '2026-12-07',
        'nightCount'  => 3,
        'guests'      => [['adults' => 2]],
        'filter' => [
            'ratePlans' => [[
                'groupId' => <groupId>,
                'hotels'  => ['exceptions' => [<hotelId>], 'default' => 'Excluded'],
            ]],
            'currencies' => ['exceptions' => ['EUR'], 'default' => 'Excluded'],
        ],
        'layout' => [
            'levels' => [[
                'name' => 'ArticleRate',
                'properties' => [
                    'Status',
                    'Availability',
                    'TotalPrice',
                    'IncludedArticles',
                    'CancellationCondition',
                    'Occupancies',
                ],
            ]],
        ],
    ],
]);
$data = json_decode($response->getBody(), true);
```

```csharp C# theme={null}
var request = new RestRequest("/Quotation/GetStayPlanning", Method.Post);
request.AddJsonBody(new
{
    arrivalDate = "2026-12-07",
    nightCount = 3,
    guests = new[] { new { adults = 2 } },
    filter = new
    {
        ratePlans = new[]
        {
            new
            {
                groupId = <groupId>,
                hotels = new { exceptions = new[] { <hotelId> }, @default = "Excluded" }
            }
        },
        currencies = new { exceptions = new[] { "EUR" }, @default = "Excluded" }
    },
    layout = new
    {
        levels = new[]
        {
            new
            {
                name = "ArticleRate",
                properties = new[]
                {
                    "Status",
                    "Availability",
                    "TotalPrice",
                    "IncludedArticles",
                    "CancellationCondition",
                    "Occupancies"
                }
            }
        }
    }
});

var response = await client.ExecuteAsync(request);
var data = JsonDocument.Parse(response.Content!).RootElement;
```

```java Java theme={null}
String body = mapper.writeValueAsString(Map.of(
    "arrivalDate", "2026-12-07",
    "nightCount", 3,
    "guests", List.of(Map.of("adults", 2)),
    "filter", Map.of(
        "ratePlans", List.of(Map.of(
            "groupId", <groupId>,
            "hotels", Map.of(
                "exceptions", List.of(<hotelId>),
                "default", "Excluded"
            )
        )),
        "currencies", Map.of(
            "exceptions", List.of("EUR"),
            "default", "Excluded"
        )
    ),
    "layout", Map.of(
        "levels", List.of(Map.of(
            "name", "ArticleRate",
            "properties", List.of(
                "Status",
                "Availability",
                "TotalPrice",
                "IncludedArticles",
                "CancellationCondition",
                "Occupancies"
            )
        ))
    )
));

HttpPost request = new HttpPost("https://{apiBaseUrl}/Planning/2018A/Quotation/GetStayPlanning");
request.setEntity(new StringEntity(body, "UTF-8"));
request.setHeader("Content-Type", "application/json");
consumer.sign(request);

JsonNode data;
try (CloseableHttpResponse response = client.execute(request)) {
    data = mapper.readTree(EntityUtils.toString(response.getEntity()));
}
```

### 2. Flatten the response into rows

The response nests entities under `ratePlans[*].hotels[*].entities[*]`. Walk the tree once and collect every entity into a flat array. Each entity becomes one row in your comparison table.

```javascript Node.js theme={null}
const rows = data.ratePlans.flatMap(rp =>
  rp.hotels.flatMap(hotel =>
    hotel.entities.map(entity => ({ hotelId: hotel.hotelId, ...entity }))
  )
);
```

```python Python theme={null}
rows = [
    {"hotelId": hotel["hotelId"], **entity}
    for rp in data["ratePlans"]
    for hotel in rp["hotels"]
    for entity in hotel["entities"]
]
```

```php PHP theme={null}
$rows = [];
foreach ($data['ratePlans'] as $rp) {
    foreach ($rp['hotels'] as $hotel) {
        foreach ($hotel['entities'] as $entity) {
            $rows[] = ['hotelId' => $hotel['hotelId']] + $entity;
        }
    }
}
```

```csharp C# theme={null}
var rows = data.GetProperty("ratePlans").EnumerateArray()
    .SelectMany(rp => rp.GetProperty("hotels").EnumerateArray()
        .SelectMany(hotel => hotel.GetProperty("entities").EnumerateArray()
            .Select(entity => new { HotelId = hotel.GetProperty("hotelId").GetInt32(), Entity = entity })
        )
    )
    .ToList();
```

```java Java theme={null}
List<Map<String, Object>> rows = new ArrayList<>();
for (JsonNode rp : data.get("ratePlans")) {
    for (JsonNode hotel : rp.get("hotels")) {
        for (JsonNode entity : hotel.get("entities")) {
            Map<String, Object> row = mapper.convertValue(entity, Map.class);
            row.put("hotelId", hotel.get("hotelId").asInt());
            rows.add(row);
        }
    }
}
```

### 3. Render the available offers

For each row where `status` is `Available`, display the room, rate, total price, and currency. The `availability` field tells you how many units remain, useful for a low-stock badge. The `articles.includedArticles` array lists meal plans and other mandatory services included in `totalPrice`.

```javascript Node.js theme={null}
rows
  .filter(row => row.status === "Available")
  .forEach(row => {
    renderQuoteRow({
      roomId: row.roomId,
      rateId: row.rateId,
      totalPrice: row.totalPrice,
      currency: row.currency,
      availability: row.availability,
      includedArticles: row.articles?.includedArticles ?? [],
      cancellation: row.cancellationCondition,
    });
  });
```

```python Python theme={null}
for row in rows:
    if row.get("status") == "Available":
        render_quote_row({
            "roomId": row["roomId"],
            "rateId": row["rateId"],
            "totalPrice": row["totalPrice"],
            "currency": row["currency"],
            "availability": row["availability"],
            "includedArticles": row.get("articles", {}).get("includedArticles", []),
            "cancellation": row.get("cancellationCondition"),
        })
```

```php PHP theme={null}
foreach ($rows as $row) {
    if (($row['status'] ?? null) === 'Available') {
        renderQuoteRow([
            'roomId'           => $row['roomId'],
            'rateId'           => $row['rateId'],
            'totalPrice'       => $row['totalPrice'],
            'currency'         => $row['currency'],
            'availability'     => $row['availability'],
            'includedArticles' => $row['articles']['includedArticles'] ?? [],
            'cancellation'     => $row['cancellationCondition'] ?? null,
        ]);
    }
}
```

```csharp C# theme={null}
foreach (var row in rows)
{
    var entity = row.Entity;
    if (entity.TryGetProperty("status", out var status)
        && status.GetString() == "Available")
    {
        RenderQuoteRow(new
        {
            roomId = entity.GetProperty("roomId").GetInt32(),
            rateId = entity.GetProperty("rateId").GetInt32(),
            totalPrice = entity.GetProperty("totalPrice").GetDecimal(),
            currency = entity.GetProperty("currency").GetString(),
            availability = entity.GetProperty("availability").GetInt32()
        });
    }
}
```

```java Java theme={null}
for (Map<String, Object> row : rows) {
    if ("Available".equals(row.get("status"))) {
        renderQuoteRow(Map.of(
            "roomId", row.get("roomId"),
            "rateId", row.get("rateId"),
            "totalPrice", row.get("totalPrice"),
            "currency", row.get("currency"),
            "availability", row.get("availability")
        ));
    }
}
```

### 4. Surface unavailable rows with the right reason

For every row where `status` is `NotAvailable`, mark the entry as not bookable rather than hiding it. A row with `status` set to `OnRequest` is bookable once the hotel confirms availability: show it as an offer on request, not as unavailable. The `unavailabilityReason` tells you why, which lets you display a more helpful tooltip than a generic "unavailable".

| Reason | Tooltip suggestion |
| - | - |
| `GuestsDoNotFitInRoom` | Too many guests for this room. |
| `MinStay` | Available, but requires a longer stay. |
| `MaxStay` | Available, but requires a shorter stay. |
| `Other` | Not available for this stay. |

## Tips

* **Ask the layout for only what you display**. The `layout.levels[*].properties` field controls how much detail the API computes per entity. Adding `Promotions` or daily breakdowns inflates the payload and the processing time. Keep the list to the columns your interface actually shows.
* **Cache by quote key**. The natural cache key is `(arrivalDate, nightCount, guests, hotelId, currency)`. A short cache cuts response time on repeat views, and the key changes the moment the guest picks new dates or a different party.

## Related

> **[Reference](/quotation-api/build/reference/stay-quotation.md)**
>
> Full schema for the Stay quotation endpoint.

> **[Best practices](/quotation-api/build/best-practices.md)**
>
> Caching, performance, and resilience patterns.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.