> ## 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.

# Dynamic price calendar

> Show the best rate for each arrival date in a range

A price calendar shows guests the cheapest available offer for every arrival date in a window, so they can pick their dates based on price. One Best Price call powers the whole grid.

## Prerequisites

Before building a calendar, make sure you have:

* A working Best Price call. If not, follow the [Quick start](/best-price-api/get-started/quick-start.md) first.
* A hotel ID and a group ID for the property you display.
* A target date window (typically 30 to 90 days).

## How it works

The Calendar endpoint returns one entry per arrival date in your requested range. Each entry contains the cheapest offer for that day. You map the response array directly onto your calendar cells: one date, one price, one availability indicator.

A single call covers the whole window. No iteration, no per-day requests.

## Build the calendar

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

### 1. Request the date range you display

Call Best Price once with the first and last dates your UI shows. The default `behaviour` of `LowestLengthOfStay` fits a browse experience: it returns the best price compatible with the shortest stay, instead of pushing the user toward longer mandatory stays.

```bash cURL theme={null}
curl --request POST \
  --url https://{apiBaseUrl}/Planning/2018A/BestPrice/Calendar \
  --header 'Authorization: OAuth ...' \
  --header 'Content-Type: application/json' \
  --data '{
    "beginDate": "2026-12-07",
    "endDate": "2027-01-05",
    "guests": [{"adults": 2}],
    "filter": {
      "ratePlan": {"hotelId": <hotelId>, "groupId": <groupId>},
      "currency": "EUR"
    }
  }'
```

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

const response = await fetch(url, {
  method: "POST",
  headers: { ...authHeaders, "Content-Type": "application/json" },
  body: JSON.stringify({
    beginDate: "2026-12-07",
    endDate: "2027-01-05",
    guests: [{ adults: 2 }],
    filter: {
      ratePlan: { hotelId: <hotelId>, groupId: <groupId> },
      currency: "EUR",
    },
  }),
});
const data = await response.json();
```

```python Python theme={null}
response = session.post(
    "https://{apiBaseUrl}/Planning/2018A/BestPrice/Calendar",
    json={
        "beginDate": "2026-12-07",
        "endDate": "2027-01-05",
        "guests": [{"adults": 2}],
        "filter": {
            "ratePlan": {"hotelId": <hotelId>, "groupId": <groupId>},
            "currency": "EUR",
        },
    },
)
data = response.json()
```

```php PHP theme={null}
$response = $client->post('Planning/2018A/BestPrice/Calendar', [
    'json' => [
        'beginDate' => '2026-12-07',
        'endDate'   => '2027-01-05',
        'guests'    => [['adults' => 2]],
        'filter'    => [
            'ratePlan' => ['hotelId' => <hotelId>, 'groupId' => <groupId>],
            'currency' => 'EUR',
        ],
    ],
]);
$data = json_decode($response->getBody(), true);
```

```csharp C# theme={null}
var request = new RestRequest("/BestPrice/Calendar", Method.Post);
request.AddJsonBody(new
{
    beginDate = "2026-12-07",
    endDate = "2027-01-05",
    guests = new[] { new { adults = 2 } },
    filter = new
    {
        ratePlan = new { hotelId = <hotelId>, groupId = <groupId> },
        currency = "EUR"
    }
});

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

```java Java theme={null}
String body = mapper.writeValueAsString(Map.of(
    "beginDate", "2026-12-07",
    "endDate", "2027-01-05",
    "guests", List.of(Map.of("adults", 2)),
    "filter", Map.of(
        "ratePlan", Map.of("hotelId", <hotelId>, "groupId", <groupId>),
        "currency", "EUR"
    )
));

HttpPost request = new HttpPost("https://{apiBaseUrl}/Planning/2018A/BestPrice/Calendar");
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. Render available dates with their price

For each entry where `status` is `Available`, display `totalPrice` with `currency`. The `arrivalDate` field tells you which calendar cell the entry belongs to.

```javascript Node.js theme={null}
data.forEach(day => {
  if (day.status === "Available") {
    renderCell(day.arrivalDate, {
      price: day.totalPrice,
      currency: day.currency,
    });
  }
});
```

```python Python theme={null}
for day in data:
    if day["status"] == "Available":
        render_cell(day["arrivalDate"], {
            "price": day["totalPrice"],
            "currency": day["currency"],
        })
```

```php PHP theme={null}
foreach ($data as $day) {
    if ($day['status'] === 'Available') {
        renderCell($day['arrivalDate'], [
            'price'    => $day['totalPrice'],
            'currency' => $day['currency'],
        ]);
    }
}
```

```csharp C# theme={null}
foreach (var day in data.EnumerateArray())
{
    if (day.GetProperty("status").GetString() == "Available")
    {
        RenderCell(day.GetProperty("arrivalDate").GetString()!, new
        {
            price = day.GetProperty("totalPrice").GetDecimal(),
            currency = day.GetProperty("currency").GetString()
        });
    }
}
```

```java Java theme={null}
for (JsonNode day : data) {
    if ("Available".equals(day.get("status").asText())) {
        renderCell(
            day.get("arrivalDate").asText(),
            Map.of(
                "price", day.get("totalPrice").decimalValue(),
                "currency", day.get("currency").asText()
            )
        );
    }
}
```

### 3. Surface promotions when the API returns them

When `originalTotalPrice` is present, the day has an active promotion. In a tight calendar grid, show only `totalPrice` and signal the promotion with a visual cue: a coloured cell, a small badge, or a corner icon.

```javascript Node.js theme={null}
if (day.originalTotalPrice) {
  markCellAsPromo(day.arrivalDate);
}
```

```python Python theme={null}
if day.get("originalTotalPrice"):
    mark_cell_as_promo(day["arrivalDate"])
```

```php PHP theme={null}
if (!empty($day['originalTotalPrice'])) {
    markCellAsPromo($day['arrivalDate']);
}
```

```csharp C# theme={null}
if (day.TryGetProperty("originalTotalPrice", out _))
{
    MarkCellAsPromo(day.GetProperty("arrivalDate").GetString()!);
}
```

```java Java theme={null}
if (day.has("originalTotalPrice") && !day.get("originalTotalPrice").isNull()) {
    markCellAsPromo(day.get("arrivalDate").asText());
}
```

### 4. Disable unavailable dates with the right reason

For every entry where `status` is `NotAvailable`, grey out the cell; show an `OnRequest` day as bookable with a distinct cue, since the hotel confirms it before booking. 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 the rooms available that day. |
| `NoArrival` | No arrival possible on this date. |
| `MinimumStayTooLong` | Available, but requires a longer stay. |
| `Other` | Sold out. |

## Tips

* **Mind the minimum stay**. The `totalPrice` is already a per-night average, so calendar cells are visually comparable without any computation on your side. What changes when `minimumStay` is greater than 1 is the booking constraint: the visitor must commit to that many nights to pay that rate. Two strategies, depending on your hotel type:
  * **City or business hotels**: force a single-night view by setting `filter.maximumStay: 1`. The API only returns days bookable as 1-night stays, so visitors never face a stay-length surprise at booking.
  * **Resorts or luxury properties**: display the price as-is and surface the constraint with a small label on the cell ("min 3 nights"). Multi-night stays are the norm here, and hiding those days would empty most of the calendar.
  * **Seasonal mix**: default to displaying as-is so you never hide bookable days, then switch to the 1-night view on date ranges where short stays are the rule.
* **Cache the response**. Best Price data is browse-heavy and changes slowly. A short cache cuts response time on repeat views.
* **Watch closedOnDeparture**. When `true`, guests cannot check out that day. It does not affect arrival, but it constrains the departure dates compatible with the arrival.

## Related

> **[Reference](/best-price-api/build/reference/calendar.md)**
>
> Full schema for every field returned by Best Price.

> **[Best practices](/best-price-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.