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

# RateHawk integration: rules, flow and mapping

> The RateHawk ETG v3 rules Omnibeds follows: search limits, stored content, hotel page and prebook, the five-call booking, name rules, async status, cancelling and mapping.

Omnibeds connects to RateHawk through the Emerging Travel Group (ETG) B2B API, version 3. Every endpoint is a POST with a JSON body, and every answer is an envelope with `status: ok` or `error`. RateHawk publishes integration requirements that partners must follow; this page lists how Omnibeds meets each one. RateHawk is treated as a confidential supplier: presenter mode only. The adapter is in `lib/suppliers/ratehawk/`.

## At a glance

| | |
| - | - |
| Hosts | Sandbox `api-sandbox.ratehawk.com`; test and production keys use `api.ratehawk.com` |
| Authentication | HTTP Basic with a key id and access token, server side only |
| How it keeps state | Chained: hotel id and `match_hash` from search, `book_hash` from the hotel page and prebook, then our `partner_order_id` |
| Offer validity | A search's `search_hash` lasts 38 minutes (RateHawk). We count an offer as old after 30 minutes, and read the hotel page again before every price check anyway |
| Rate limits | Per endpoint (for example 10 searches and 30 bookings a minute in the sandbox), reported in `X-RateLimit-*` headers |
| Daily calls | Our own safety cap of 1,000 a day |
| Time limits | Search: we send `timeout: 20` and wait 25 s. Prebook: up to 60 s. Other calls: 30 s |
| Presenter mode | Required for everything |

## Their flow and ours

| Our step | RateHawk calls | What Omnibeds does with it |
| - | - | - |
| Search | `search/serp/geo/` (a circle) or `search/serp/hotels/` (given hotels) | Rates per hotel id; names, positions and photos come from our stored content |
| Price check | `search/hp/` (hotel page), then `hotel/prebook/` | A fresh `book_hash`, then RateHawk confirms the rate and price |
| Book | Hotel page, prebook, `hotel/order/booking/form/`, `hotel/order/booking/finish/`, then its status | Five steps; see below |
| Live view | Order info, by our `partner_order_id` | Shows how RateHawk holds the order |
| Reconciliation | Booking status, by `partner_order_id` | Finished is confirmed; a final error closes it as failed; processing stays pending |
| Cancel | `hotel/order/cancel/` with `partner_order_id` | See "When something goes wrong" |
| Hotel content | Content API `hotel_content_by_ids` | Stored in our own table, in batches of 50 |

## Booking, step by step

Back
Next

## Must do

**1. Stay within RateHawk's search limits**

Up to 9 rooms, 6 adults and 4 children (aged 0 to 17) a room, 30 nights, and check-in at most 730 days ahead. A search outside these is explained to the agent and never sent.

**2. Send the guests' residence on every search**

In lower case, from the search's residence field, else the agency's default residence, else its country. Searches also send the guests room by room (adults and children's ages), `language: en` and `currency: EUR`.

**3. Keep hotel content on our side**

Searches return only hotel ids. Content lives in our database, is refreshed on the schedule set in the admin panel (or with **Reload now**), and is never fetched while a search or booking is running. Hotels not stored yet are loaded right after the search and show from the next one.

**4. Show the hotel's rules**

RateHawk's metapolicy (deposits, pet fees, parking, extra beds...) is shown line by line in its own wording, together with its free text about the hotel's rules.

**5. Read the hotel page and prebook before every booking**

Never book from a search result: the hotel page gives a fresh `book_hash`, and the prebook confirms it.

**6. Follow the name rules**

First and last names of 1 to 50 characters: letters in any script, spaces and the characters `' - , . ’`, no digits. No placeholder names, and at least one adult with a real name in every room. Checked in the guest step, before any call.

**7. Identify the order with a UUID**

Our `partner_order_id` is a UUID. Cancel, status and lookups all use it.

**8. Send what the booking form asks for**

The agent's IP address (`user_ip`), one fixed agency email (RateHawk's rule for B2B: not the guest's), and a phone of 5 to 35 characters (the agency's, or a placeholder number). When the hotel requires a gender, `unknown` is sent, which is one of RateHawk's values.

**9. Respect each endpoint's limit**

Omnibeds remembers what each endpoint's `X-RateLimit-*` headers last said and doesn't send a call that would certainly be refused. A 429 from RateHawk is still read and explained.

## Never do

> [!WARNING]
>
> * Never send a placeholder name ("Dummy", "Test"). A guest whose name isn't known yet is left out of the booking instead.
> * Never book a higher price than the agent agreed. The booking prebook allows no increase; a higher price sends the agent back to the price check.
> * Never treat a timeout, `unknown` or a 5xx as a failed booking. RateHawk's rules say the booking may still go through, so Omnibeds keeps asking its status.
> * Never call the Content API during a search or a booking.
> * Never spend the limits on a health check: health makes no call.

## How its data becomes ours

| RateHawk | Omnibeds |
| - | - |
| A rate (covers the whole stay, every room) | One rate |
| `show_amount` in the currency we asked for | The price we show and put the margin on |
| Taxes | Each in its own currency; those not included are shown as paid at the hotel |
| Cancellation layers (UTC) | Cancellation timeline. A free layer (amount 0) isn't a penalty segment |
| Meal values | Our five boards, never upgrading: a plan maps to the most it certainly includes. Dinner or lunch alone is room only; RateHawk's wording stays on the rate |
| Room name | Shown in full, as RateHawk asks |
| Times without a zone | Read as UTC |
| Payment types | The agency's deposit (the B2B way), else the first type RateHawk offers |
| `match_hash` that changes between search and hotel page | The rate is found again by room, meal and refundability |

## When something goes wrong

| RateHawk answers | Omnibeds |
| - | - |
| The rate is no longer on the hotel page, or the prebook is refused | The offer counts as expired: Omnibeds searches again and the agent confirms the new price |
| The booking form answers a timeout, `unknown` or a 5xx | Sent again with the same `partner_order_id`, up to 3 times |
| The status is still processing after 20 s | The booking is saved as pending; reconciliation finishes it (the sandbox takes about a minute and a half) |
| A final booking error | Said in plain words, for example: the room sold out while booking, the agency's deposit is too low, the hotel's provider refused, the booking form expired |
| An order not listed yet, a few seconds after booking | Asked again a few times before saying so, for lookups and cancellations |
| A cancellation times out | Asked once more; it is safe, it's the same order |
| A field refused by validation | RateHawk's own words from `debug.validation_error` are shown |

| Step | RateHawk call | Detail |
| - | - | - |
| 1 | Name check (no call) | Names follow RateHawk's rules; otherwise the booking stops with GUEST\_NAMES (422) |
| 2 | `search/hp/` | The hotel page gives a fresh book\_hash for the chosen rate |
| 3 | `hotel/prebook/` | Allows no price increase; a higher price returns the agent to the price check |
| 4 | `hotel/order/booking/form/` | Our UUID partner\_order\_id, the book\_hash and the agent's IP; retried up to 3 times on timeout, unknown or 5xx |
| 5 | `hotel/order/booking/finish/` | Agency email and phone, every room's real-named guests, the deposit payment |
| 6 | Booking status | Asked every 2 s for up to 20 s; then confirmed, failed, or pending for reconciliation |


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