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

# Nightly rate refresh

> Refresh an internal rates database from D-EDGE on a nightly schedule

A nightly rate refresh pulls daily availability and prices into a partner-side database on a schedule, so downstream search and booking interfaces can serve their own visitors without round-tripping to D-EDGE on every query. One Daily Rates call per hotel per refresh covers the full window.

## Prerequisites

Before building the refresh job, make sure you have:

* A working Daily Rates call. If not, follow the [Quick start](/daily-rates-api/get-started/quick-start.md) first.
* A hotel ID and a group ID for each property you sync.
* A target window (typically 90 days for a single daily refresh; longer when you split the cadence, see Tips).
* A storage layer keyed by `(hotelId, roomId, rateId, date)`.

## How it works

The Per day endpoint returns availability, prices, and restrictions for every day in your window, grouped by rate plan, hotel, and entity. Each entity is one room and rate combination, with daily fields encoded as `{ value, start, end }` segments, where `end` is exclusive (consecutive segments share their boundary date). A single call covers the whole window.

For a refresh job, the response shape works in your favour: segments compress consecutive days where the value does not change, so most entities collapse to a few rows even over long windows. Expand each segment to one row per day before writing to storage, or store the segments as-is if your downstream queries are range-aware.

## Build the refresh

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

### 1. Fetch the window for one hotel

Send one request to `/DailyRates/PerDay` per hotel, with the date window you sync and a `layout` listing the properties you store. Start minimal: extra properties cost time on every refresh, so add new ones only when downstream features actually need them.

```bash cURL theme={null}
curl --request POST \
  --url https://{apiBaseUrl}/Planning/2018A/DailyRates/PerDay \
  --header 'Authorization: OAuth ...' \
  --header 'Content-Type: application/json' \
  --data '{
    "beginDate": "2026-12-07",
    "endDate": "2027-03-08",
    "guests": [{"adults": 2}],
    "filter": {
      "ratePlans": [
        {
          "hotels": {"exceptions": [<hotelId>], "default": "Excluded"},
          "groupId": <groupId>
        }
      ],
      "currencies": {"exceptions": ["EUR"], "default": "Excluded"}
    },
    "layout": {
      "levels": [
        {
          "name": "ArticleRate",
          "properties": [
            "Status",
            "Availability",
            "TotalPrice",
            "MinimumStayOnArrival",
            "MaximumStayOnArrival",
            "ClosedOnArrival",
            "ClosedOnDeparture",
            "Occupancies"
          ]
        }
      ]
    }
  }'
```

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

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

```python Python theme={null}
response = session.post(
    "https://{apiBaseUrl}/Planning/2018A/DailyRates/PerDay",
    json={
        "beginDate": "2026-12-07",
        "endDate": "2027-03-08",
        "guests": [{"adults": 2}],
        "filter": {
            "ratePlans": [
                {
                    "hotels": {"exceptions": [<hotelId>], "default": "Excluded"},
                    "groupId": <groupId>,
                }
            ],
            "currencies": {"exceptions": ["EUR"], "default": "Excluded"},
        },
        "layout": {
            "levels": [
                {
                    "name": "ArticleRate",
                    "properties": [
                        "Status",
                        "Availability",
                        "TotalPrice",
                        "MinimumStayOnArrival",
                        "MaximumStayOnArrival",
                        "ClosedOnArrival",
                        "ClosedOnDeparture",
                        "Occupancies",
                    ],
                }
            ]
        },
    },
)
data = response.json()
```

```php PHP theme={null}
$response = $client->post('Planning/2018A/DailyRates/PerDay', [
    'json' => [
        'beginDate' => '2026-12-07',
        'endDate'   => '2027-03-08',
        'guests'    => [['adults' => 2]],
        'filter'    => [
            'ratePlans' => [
                [
                    'hotels'  => ['exceptions' => [<hotelId>], 'default' => 'Excluded'],
                    'groupId' => <groupId>,
                ],
            ],
            'currencies' => ['exceptions' => ['EUR'], 'default' => 'Excluded'],
        ],
        'layout' => [
            'levels' => [
                [
                    'name'       => 'ArticleRate',
                    'properties' => [
                        'Status',
                        'Availability',
                        'TotalPrice',
                        'MinimumStayOnArrival',
                        'MaximumStayOnArrival',
                        'ClosedOnArrival',
                        'ClosedOnDeparture',
                        'Occupancies',
                    ],
                ],
            ],
        ],
    ],
]);
$data = json_decode($response->getBody(), true);
```

```csharp C# theme={null}
var request = new RestRequest("/DailyRates/PerDay", Method.Post);
request.AddJsonBody(new
{
    beginDate = "2026-12-07",
    endDate = "2027-03-08",
    guests = new[] { new { adults = 2 } },
    filter = new
    {
        ratePlans = new[]
        {
            new
            {
                hotels  = new { exceptions = new[] { <hotelId> }, @default = "Excluded" },
                groupId = <groupId>
            }
        },
        currencies = new { exceptions = new[] { "EUR" }, @default = "Excluded" }
    },
    layout = new
    {
        levels = new[]
        {
            new
            {
                name = "ArticleRate",
                properties = new[]
                {
                    "Status",
                    "Availability",
                    "TotalPrice",
                    "MinimumStayOnArrival",
                    "MaximumStayOnArrival",
                    "ClosedOnArrival",
                    "ClosedOnDeparture",
                    "Occupancies"
                }
            }
        }
    }
});

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-03-08",
    "guests", List.of(Map.of("adults", 2)),
    "filter", Map.of(
        "ratePlans", List.of(Map.of(
            "hotels", Map.of("exceptions", List.of(<hotelId>), "default", "Excluded"),
            "groupId", <groupId>
        )),
        "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",
                "MinimumStayOnArrival",
                "MaximumStayOnArrival",
                "ClosedOnArrival",
                "ClosedOnDeparture",
                "Occupancies"
            )
        ))
    )
));

HttpPost request = new HttpPost("https://{apiBaseUrl}/Planning/2018A/DailyRates/PerDay");
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. Expand temporal segments into daily rows

Walk every entity in the response, then expand each `{ value, start, end }` segment into one record per day. Group by `(hotelId, roomId, rateId, date)` so your storage stays keyed at the day level.

```javascript Node.js theme={null}
function eachDay(start, end) {
  const out = [];
  let d = new Date(start);
  const endD = new Date(end);
  while (d < endD) {
    out.push(d.toISOString().slice(0, 10));
    d.setUTCDate(d.getUTCDate() + 1);
  }
  return out;
}

function expandSegments(entity, hotelId) {
  const rows = [];

  const fields = ["status", "availability", "totalPrice", "minimumStayOnArrival",
                  "maximumStayOnArrival", "closedOnArrival", "closedOnDeparture"];

  const days = new Map();
  for (const field of fields) {
    for (const segment of entity[field] ?? []) {
      for (const date of eachDay(segment.start, segment.end)) {
        if (!days.has(date)) days.set(date, { date });
        days.get(date)[field] = segment.value;
      }
    }
  }

  for (const day of days.values()) {
    rows.push({
      hotelId,
      roomId: entity.roomId,
      rateId: entity.rateId,
      ...day,
    });
  }
  return rows;
}

const rows = data.ratePlans
  .flatMap(rp => rp.hotels)
  .flatMap(h => h.entities.flatMap(e => expandSegments(e, h.hotelId)));
```

```python Python theme={null}
from datetime import date, timedelta

def each_day(start, end):
    d, end_d = date.fromisoformat(start), date.fromisoformat(end)
    while d < end_d:
        yield d.isoformat()
        d += timedelta(days=1)

def expand_segments(entity, hotel_id):
    fields = ["status", "availability", "totalPrice", "minimumStayOnArrival",
              "maximumStayOnArrival", "closedOnArrival", "closedOnDeparture"]
    days = {}
    for field in fields:
        for segment in entity.get(field, []):
            for d in each_day(segment["start"], segment["end"]):
                days.setdefault(d, {"date": d})[field] = segment["value"]

    return [
        {"hotelId": hotel_id, "roomId": entity["roomId"], "rateId": entity["rateId"], **day}
        for day in days.values()
    ]

rows = [
    row
    for rp in data["ratePlans"]
    for hotel in rp["hotels"]
    for entity in hotel["entities"]
    for row in expand_segments(entity, hotel["hotelId"])
]
```

```php PHP theme={null}
function eachDay(string $start, string $end): array {
    $days = [];
    $d = new DateTime($start);
    $endD = new DateTime($end);
    while ($d < $endD) {
        $days[] = $d->format('Y-m-d');
        $d->modify('+1 day');
    }
    return $days;
}

function expandSegments(array $entity, int $hotelId): array {
    $fields = ['status', 'availability', 'totalPrice', 'minimumStayOnArrival',
               'maximumStayOnArrival', 'closedOnArrival', 'closedOnDeparture'];
    $days = [];
    foreach ($fields as $field) {
        foreach ($entity[$field] ?? [] as $segment) {
            foreach (eachDay($segment['start'], $segment['end']) as $date) {
                $days[$date]['date'] = $date;
                $days[$date][$field] = $segment['value'];
            }
        }
    }
    $rows = [];
    foreach ($days as $day) {
        $rows[] = array_merge(
            ['hotelId' => $hotelId, 'roomId' => $entity['roomId'], 'rateId' => $entity['rateId']],
            $day
        );
    }
    return $rows;
}

$rows = [];
foreach ($data['ratePlans'] as $rp) {
    foreach ($rp['hotels'] as $hotel) {
        foreach ($hotel['entities'] as $entity) {
            $rows = array_merge($rows, expandSegments($entity, $hotel['hotelId']));
        }
    }
}
```

```csharp C# theme={null}
static IEnumerable<string> EachDay(string start, string end)
{
    var d = DateOnly.Parse(start);
    var endD = DateOnly.Parse(end);
    while (d < endD)
    {
        yield return d.ToString("yyyy-MM-dd");
        d = d.AddDays(1);
    }
}

var rows = new List<Dictionary<string, object>>();
var fields = new[] { "status", "availability", "totalPrice", "minimumStayOnArrival",
                     "maximumStayOnArrival", "closedOnArrival", "closedOnDeparture" };

foreach (var rp in data.GetProperty("ratePlans").EnumerateArray())
foreach (var hotel in rp.GetProperty("hotels").EnumerateArray())
foreach (var entity in hotel.GetProperty("entities").EnumerateArray())
{
    var days = new Dictionary<string, Dictionary<string, object>>();
    foreach (var field in fields)
    {
        if (!entity.TryGetProperty(field, out var segments)) continue;
        foreach (var segment in segments.EnumerateArray())
        {
            foreach (var date in EachDay(
                segment.GetProperty("start").GetString()!,
                segment.GetProperty("end").GetString()!))
            {
                if (!days.ContainsKey(date)) days[date] = new() { ["date"] = date };
                days[date][field] = segment.GetProperty("value").ToString();
            }
        }
    }
    foreach (var day in days.Values)
    {
        day["hotelId"] = hotel.GetProperty("hotelId").GetInt32();
        day["roomId"] = entity.GetProperty("roomId").GetInt32();
        day["rateId"] = entity.GetProperty("rateId").GetInt32();
        rows.Add(day);
    }
}
```

```java Java theme={null}
static List<String> eachDay(String start, String end) {
    var days = new ArrayList<String>();
    var d = LocalDate.parse(start);
    var endD = LocalDate.parse(end);
    while (d.isBefore(endD)) {
        days.add(d.toString());
        d = d.plusDays(1);
    }
    return days;
}

var fields = List.of("status", "availability", "totalPrice", "minimumStayOnArrival",
                     "maximumStayOnArrival", "closedOnArrival", "closedOnDeparture");
var rows = new ArrayList<Map<String, Object>>();

for (JsonNode rp : data.get("ratePlans")) {
    for (JsonNode hotel : rp.get("hotels")) {
        int hotelId = hotel.get("hotelId").asInt();
        for (JsonNode entity : hotel.get("entities")) {
            Map<String, Map<String, Object>> days = new HashMap<>();
            for (String field : fields) {
                JsonNode segments = entity.get(field);
                if (segments == null) continue;
                for (JsonNode segment : segments) {
                    for (String date : eachDay(
                        segment.get("start").asText(),
                        segment.get("end").asText())) {
                        days.computeIfAbsent(date, k -> new HashMap<>()).put("date", date);
                        days.get(date).put(field, segment.get("value").asText());
                    }
                }
            }
            for (Map<String, Object> day : days.values()) {
                day.put("hotelId", hotelId);
                day.put("roomId", entity.get("roomId").asInt());
                day.put("rateId", entity.get("rateId").asInt());
                rows.add(day);
            }
        }
    }
}
```

### 3. Upsert into your storage

Write the expanded rows into your database, keyed by `(hotelId, roomId, rateId, date)`. Treat each refresh as the new source of truth: rows that were present last cycle but absent this cycle should either be deleted or marked as inactive, depending on your downstream semantics.

Wrap each hotel's upsert in a transaction so a partial write does not leave a torn state if the refresh crashes mid-hotel.

### 4. Schedule the refresh

Run the refresh on a cadence that matches your downstream's freshness requirement. Nightly during off-peak hours is the common baseline; the Tips below cover when to split the cadence into near-term and far-term cycles. Stagger hotels across the window with a random offset rather than firing every property at the same minute.

Surface a manual trigger in your back office so revenue managers can force an out-of-cycle sync after a pricing rule change, instead of waiting for the next scheduled run.

## Tips

* **Keep the previous snapshot on failure**. If a refresh fails for one hotel, do not wipe its previous data: serve stale rather than empty. Alert if the same hotel fails several cycles in a row.
* **Watch for rate plan churn**. A rate plan that disappears between refreshes means either a real configuration change or a transient filter mismatch. Deleting old rows immediately can mask the difference. Mark them inactive first and clean up after a grace period.
* **Right-size the window and the cadence**. Long windows are slow to pull, and rates 6 to 12 months out barely move between cycles. Pulling the full horizon every night wastes traffic. 90 days, refreshed daily, is a comfortable starting point. To cover further out, split the refresh into a frequent near-term cycle (next 90 days, several times a day) and a slower far-term one (the rest, daily or weekly is usually enough).

## Related

> **[Reference](/daily-rates-api/build/reference/per-day.md)**
>
> Full schema for the Per day endpoint.

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