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

# Member rate showcase

> Show the value of being a loyalty member with a side-by-side rate comparison

A side-by-side comparison of public rate versus member rate is the strongest argument for joining your loyalty programme. Two Best Price Rates calls give you both prices; the difference becomes the headline number in your widget.

## Prerequisites

Before building a member rate widget, 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.
* A target date or date window.
* An active loyalty programme on the property, configured by D-EDGE.

## How it works

The Best Price Rates endpoint returns one entry per rate and requested strategy, with the best price found over your date window and the arrival date it applies to. The `loyaltyProgram` filter tells the API which membership tier to consider when picking the best rate:

* **Without `loyaltyProgram`** (or `membershipLevel: 0`): the API returns rates open to non-members. The cheapest is your public BAR.
* **With `loyaltyProgram: { membershipLevel: N }`**: the API includes rates restricted to members at tier N. The cheapest is your member rate at that tier.

Run both calls in parallel, pick the cheapest entry from each response, and the difference is the saving you headline to non-members.

## Build the showcase

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

### 1. Run a public and a member call in parallel

Same date range, same party in both calls. The only difference is the `loyaltyProgram` filter on the member call.

```bash cURL theme={null}
# Public call
curl --request POST \
  --url https://{apiBaseUrl}/Planning/2018A/BestPrice/Rates \
  --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"
    }
  }'

# Member call (same body plus loyaltyProgram in filter)
curl --request POST \
  --url https://{apiBaseUrl}/Planning/2018A/BestPrice/Rates \
  --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",
      "loyaltyProgram": {"membershipLevel": 1}
    }
  }'
```

```javascript Node.js theme={null}
const url = "https://{apiBaseUrl}/Planning/2018A/BestPrice/Rates";
const baseFilter = {
  ratePlan: { hotelId: <hotelId>, groupId: <groupId> },
  currency: "EUR",
};
const baseRequest = {
  beginDate: "2026-12-07",
  endDate: "2027-01-05",
  guests: { adults: 2 },
};

const callRates = (filter) =>
  fetch(url, {
    method: "POST",
    headers: { ...authHeaders, "Content-Type": "application/json" },
    body: JSON.stringify({ ...baseRequest, filter }),
  }).then(r => r.json());

const [publicRates, memberRates] = await Promise.all([
  callRates(baseFilter),
  callRates({ ...baseFilter, loyaltyProgram: { membershipLevel: 1 } }),
]);
```

```python Python theme={null}
from concurrent.futures import ThreadPoolExecutor

url = "https://{apiBaseUrl}/Planning/2018A/BestPrice/Rates"
base_filter = {
    "ratePlan": {"hotelId": <hotelId>, "groupId": <groupId>},
    "currency": "EUR",
}
base_request = {
    "beginDate": "2026-12-07",
    "endDate": "2027-01-05",
    "guests": {"adults": 2},
}

def call_rates(filter):
    return session.post(url, json={**base_request, "filter": filter}).json()

with ThreadPoolExecutor() as pool:
    public_future = pool.submit(call_rates, base_filter)
    member_future = pool.submit(
        call_rates,
        {**base_filter, "loyaltyProgram": {"membershipLevel": 1}},
    )
    public_rates = public_future.result()
    member_rates = member_future.result()
```

```php PHP theme={null}
use GuzzleHttp\Promise;

$baseFilter = [
    'ratePlan' => ['hotelId' => <hotelId>, 'groupId' => <groupId>],
    'currency' => 'EUR',
];
$baseRequest = [
    'beginDate' => '2026-12-07',
    'endDate'   => '2027-01-05',
    'guests'    => ['adults' => 2],
];

$promises = [
    'public' => $client->postAsync('Planning/2018A/BestPrice/Rates', [
        'json' => $baseRequest + ['filter' => $baseFilter],
    ]),
    'member' => $client->postAsync('Planning/2018A/BestPrice/Rates', [
        'json' => $baseRequest + [
            'filter' => $baseFilter + ['loyaltyProgram' => ['membershipLevel' => 1]],
        ],
    ]),
];

$results = Promise\Utils::unwrap($promises);
$publicRates = json_decode($results['public']->getBody(), true);
$memberRates = json_decode($results['member']->getBody(), true);
```

```csharp C# theme={null}
var ratePlan = new { hotelId = <hotelId>, groupId = <groupId> };

async Task<JsonElement> CallRates(object filter)
{
    var request = new RestRequest("/BestPrice/Rates", Method.Post);
    request.AddJsonBody(new
    {
        beginDate = "2026-12-07",
        endDate = "2027-01-05",
        guests = new { adults = 2 },
        filter = filter
    });
    var response = await client.ExecuteAsync(request);
    return JsonDocument.Parse(response.Content!).RootElement;
}

var publicTask = CallRates(new { ratePlan, currency = "EUR" });
var memberTask = CallRates(new
{
    ratePlan,
    currency = "EUR",
    loyaltyProgram = new { membershipLevel = 1 }
});

await Task.WhenAll(publicTask, memberTask);
var publicRates = publicTask.Result;
var memberRates = memberTask.Result;
```

```java Java theme={null}
String url = "https://{apiBaseUrl}/Planning/2018A/BestPrice/Rates";
Map<String, Object> ratePlan = Map.of("hotelId", <hotelId>, "groupId", <groupId>);

java.util.function.Function<Map<String, Object>, JsonNode> callRates = filter -> {
    try {
        String body = mapper.writeValueAsString(Map.of(
            "beginDate", "2026-12-07",
            "endDate", "2027-01-05",
            "guests", Map.of("adults", 2),
            "filter", filter
        ));
        HttpPost request = new HttpPost(url);
        request.setEntity(new StringEntity(body, "UTF-8"));
        request.setHeader("Content-Type", "application/json");
        consumer.sign(request);
        try (CloseableHttpResponse response = client.execute(request)) {
            return mapper.readTree(EntityUtils.toString(response.getEntity()));
        }
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
};

CompletableFuture<JsonNode> publicFuture = CompletableFuture.supplyAsync(() ->
    callRates.apply(Map.of("ratePlan", ratePlan, "currency", "EUR"))
);
CompletableFuture<JsonNode> memberFuture = CompletableFuture.supplyAsync(() ->
    callRates.apply(Map.of(
        "ratePlan", ratePlan,
        "currency", "EUR",
        "loyaltyProgram", Map.of("membershipLevel", 1)
    ))
);

CompletableFuture.allOf(publicFuture, memberFuture).join();
JsonNode publicRates = publicFuture.join();
JsonNode memberRates = memberFuture.join();
```

### 2. Pick the cheapest entry from each response

For each response, filter to entries where `status` is `Available` or `OnRequest` and pick the smallest `totalPrice`. That entry carries the headline price for its side of the comparison.

```javascript Node.js theme={null}
function cheapest(response) {
  const bookable = response.filter(
    d => d.status === "Available" || d.status === "OnRequest"
  );
  if (bookable.length === 0) return null;
  return bookable.reduce((a, b) => (a.totalPrice <= b.totalPrice ? a : b));
}

const publicCheapest = cheapest(publicRates);
const memberCheapest = cheapest(memberRates);
```

```python Python theme={null}
def cheapest(response):
    bookable = [d for d in response if d["status"] in ("Available", "OnRequest")]
    if not bookable:
        return None
    return min(bookable, key=lambda d: d["totalPrice"])

public_cheapest = cheapest(public_rates)
member_cheapest = cheapest(member_rates)
```

```php PHP theme={null}
function cheapest(array $response): ?array {
    $bookable = array_filter(
        $response,
        fn($d) => in_array($d['status'], ['Available', 'OnRequest'], true)
    );
    if (empty($bookable)) return null;
    usort($bookable, fn($a, $b) => $a['totalPrice'] <=> $b['totalPrice']);
    return $bookable[0];
}

$publicCheapest = cheapest($publicRates);
$memberCheapest = cheapest($memberRates);
```

```csharp C# theme={null}
JsonElement? Cheapest(JsonElement response)
{
    var bookable = response.EnumerateArray()
        .Where(d =>
        {
            var s = d.GetProperty("status").GetString();
            return s == "Available" || s == "OnRequest";
        })
        .ToList();
    if (bookable.Count == 0) return null;
    return bookable.OrderBy(d => d.GetProperty("totalPrice").GetDecimal()).First();
}

var publicCheapest = Cheapest(publicRates);
var memberCheapest = Cheapest(memberRates);
```

```java Java theme={null}
java.util.function.Function<JsonNode, JsonNode> cheapest = response -> {
    JsonNode best = null;
    for (JsonNode entry : response) {
        String status = entry.get("status").asText();
        if (!"Available".equals(status) && !"OnRequest".equals(status)) continue;
        if (best == null
            || entry.get("totalPrice").decimalValue()
                .compareTo(best.get("totalPrice").decimalValue()) < 0) {
            best = entry;
        }
    }
    return best;
};

JsonNode publicCheapest = cheapest.apply(publicRates);
JsonNode memberCheapest = cheapest.apply(memberRates);
```

### 3. Render the comparison

Display both prices with the saving as the headline. If the member rate is not better than the public rate (rare but possible), hide the saving line rather than showing zero or negative.

```javascript Node.js theme={null}
if (publicCheapest && memberCheapest) {
  const saving = publicCheapest.totalPrice - memberCheapest.totalPrice;
  renderComparison({
    publicPrice: publicCheapest.totalPrice,
    memberPrice: memberCheapest.totalPrice,
    currency: memberCheapest.currency,
    saving: saving > 0 ? saving : null,
  });
}
```

```python Python theme={null}
if public_cheapest and member_cheapest:
    saving = public_cheapest["totalPrice"] - member_cheapest["totalPrice"]
    render_comparison({
        "publicPrice": public_cheapest["totalPrice"],
        "memberPrice": member_cheapest["totalPrice"],
        "currency": member_cheapest["currency"],
        "saving": saving if saving > 0 else None,
    })
```

```php PHP theme={null}
if ($publicCheapest && $memberCheapest) {
    $saving = $publicCheapest['totalPrice'] - $memberCheapest['totalPrice'];
    renderComparison([
        'publicPrice' => $publicCheapest['totalPrice'],
        'memberPrice' => $memberCheapest['totalPrice'],
        'currency'    => $memberCheapest['currency'],
        'saving'      => $saving > 0 ? $saving : null,
    ]);
}
```

```csharp C# theme={null}
if (publicCheapest.HasValue && memberCheapest.HasValue)
{
    var publicPrice = publicCheapest.Value.GetProperty("totalPrice").GetDecimal();
    var memberPrice = memberCheapest.Value.GetProperty("totalPrice").GetDecimal();
    var saving = publicPrice - memberPrice;
    RenderComparison(new
    {
        publicPrice,
        memberPrice,
        currency = memberCheapest.Value.GetProperty("currency").GetString(),
        saving = saving > 0 ? (decimal?)saving : null
    });
}
```

```java Java theme={null}
if (publicCheapest != null && memberCheapest != null) {
    BigDecimal publicPrice = publicCheapest.get("totalPrice").decimalValue();
    BigDecimal memberPrice = memberCheapest.get("totalPrice").decimalValue();
    BigDecimal saving = publicPrice.subtract(memberPrice);
    renderComparison(Map.of(
        "publicPrice", publicPrice,
        "memberPrice", memberPrice,
        "currency", memberCheapest.get("currency").asText(),
        "saving", saving.signum() > 0 ? saving : null
    ));
}
```

## Tips

* **Match every other parameter between the two calls**. Date range, party, room filter, currency. Any difference there contaminates the comparison.
* **Pick the right tier to compare against**. Most chains run a basic entry-level tier (`1`) and elite tiers above (`2`, `3`, etc., typically Silver or Gold). For the entry-point widget, tier 1 is the right choice: it sets the floor a non-member crosses by signing up. Showcase higher tiers separately on the loyalty programme page.
* **Cache both calls**. Use a key that includes the membership level. The same widget refreshed across many visitors hits the cache and stays fast.
* **Handle the no-saving case**. Some dates have no member-only deal active. If the member rate matches or exceeds the public rate, hide the saving label rather than showing "save €0", which undermines the value prop.
* **Be transparent about what is included**. If your public rate excludes breakfast and the member rate includes it, surface that in the comparison. Otherwise the visitor feels misled when they reach the booking step.

## Related

> **[Reference](/best-price-api/build/reference/rates.md)**
>
> Full schema for the Rates endpoint.

> **[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.