1. Getting connected
The hotel starts the connection. In its Experience Jharkhand vendor portal it opens Channel Manager, chooses you, picks OpenTravel (OTA) XML as the message format, and receives three things to send you:
| Item | Example |
|---|---|
| Endpoint | https://www.experiencejharkhand.in/api/channel/ota |
| HotelCode | EJ-HTL-0002 |
| Key | ejck_1a2b3c4d_… |
Every message is a POST to the one endpoint, as XML. Send it bare, or in a SOAP 1.1 or 1.2 envelope. We answer in the same form: a SOAP request gets a SOAP answer. Namespaces are accepted but not required.
Authentication
Send the key in whichever way your system already does. We use the first of these that is present:
- The
X-EJ-Channel-Key: <key>header, orAuthorization: Bearer <key>. - HTTP Basic authentication, with the key as the password. The username is ignored; the HotelCode is a good choice.
- A WS-Security
UsernameTokenin the SOAP header, with the key as thePassword(PasswordText). POS/Source/RequestorID/@MessagePassword.
The key opens one hotel. The message's HotelCode must be that hotel's code; any other code gets error 392.
- One key per hotel. Keep it secret: the hotel sees it once. If it leaks, the hotel issues a new one, and the old key stops working at once.
- Testing first. A new connection starts in testing: updates are checked and staged, not applied, and the answer carries a warning saying so. The hotel compares the staged values with its calendar and goes live, which applies the latest staged values.
Check your key
<OTA_PingRQ xmlns="http://www.opentravel.org/OTA/2003/05" EchoToken="1">
<EchoData>hello</EchoData>
</OTA_PingRQ>The answer is an OTA_PingRS with <Success/> and your EchoData.
2. Room and rate codes
Send OTA_HotelDescriptiveInfoRQ to read the hotel's rooms and rate plans:
<OTA_HotelDescriptiveInfoRQ xmlns="http://www.opentravel.org/OTA/2003/05" EchoToken="2">
<HotelDescriptiveInfos>
<HotelDescriptiveInfo HotelCode="EJ-HTL-0002"/>
</HotelDescriptiveInfos>
</OTA_HotelDescriptiveInfoRQ>The answer lists each room as a GuestRoom whose Code is the InvTypeCode you send. Rate plans are under TPA_Extensions/RatePlans, each with its RatePlanCode and the rooms it's sold on:
<GuestRoom Code="DLX" MaxOccupancy="4" MaxAdultOccupancy="3" MaxChildOccupancy="2" Quantity="5">
<TypeRoom Name="Deluxe Room" RoomTypeCode="DLX" StandardOccupancy="2"/>
</GuestRoom>
…
<RatePlan RatePlanCode="BASE" RatePlanName="Room only" MealPlanCode="EP" InvTypeCodes="DLX STE"/>
<RatePlan RatePlanCode="CP" RatePlanName="Room with breakfast" MealPlanCode="CP" InvTypeCodes="DLX STE"/>BASEis the room-only rate, sold on every room.- Your own codes work too. Send your usual codes, and the hotel matches them to its rooms and rate plans in its Channel Manager screen. A code we don't recognise is refused (errors
402and249), and it then appears in that screen for the hotel to match. Once it's matched, send the message again. The answer'sTPA_Extensions/Mappingslists each code the hotel has matched.
3. Rates, availability and restrictions
Every notif message is checked in full before anything is written. If any part of a message is wrong, none of it is applied, and the answer lists every error, each with a RecordID: the position (from 1) of the message element it refers to. Fix it and send the whole message again.
Dates and days of the week
StatusApplicationControl says which room, which rate plan and which dates:
<StatusApplicationControl Start="2026-11-01" End="2026-11-30" InvTypeCode="DLX" RatePlanCode="CP"
Mon="false" Tue="false" Weds="false" Thur="false" Fri="true" Sat="true" Sun="false"/>StartandEndare both included, and at most 731 days apart. Dates before yesterday are skipped, because they can't be sold.- With any day flag present, only the days flagged
true(or1) are updated.Weds/WedandThur/Thuboth work. InvCodeis accepted in place ofInvTypeCode.
Availability and restrictions: OTA_HotelAvailNotifRQ
<OTA_HotelAvailNotifRQ xmlns="http://www.opentravel.org/OTA/2003/05" EchoToken="3" Version="1.0">
<AvailStatusMessages HotelCode="EJ-HTL-0002">
<AvailStatusMessage BookingLimit="4">
<StatusApplicationControl Start="2026-11-01" End="2026-11-03" InvTypeCode="DLX"/>
</AvailStatusMessage>
<AvailStatusMessage>
<StatusApplicationControl Start="2026-11-10" End="2026-11-10" InvTypeCode="STE"/>
<RestrictionStatus Status="Close" Restriction="Master"/>
</AvailStatusMessage>
<AvailStatusMessage>
<StatusApplicationControl Start="2026-11-20" End="2026-11-22" InvTypeCode="DLX"/>
<RestrictionStatus Status="Close" Restriction="Arrival"/>
<LengthsOfStay>
<LengthOfStay MinMaxMessageType="SetMinLOS" Time="2" TimeUnit="Day"/>
</LengthsOfStay>
</AvailStatusMessage>
</AvailStatusMessages>
</OTA_HotelAvailNotifRQ>| You send | What it does | |
|---|---|---|
BookingLimit="n" | Rooms left to sell. Availability belongs to the room, so it's shared by every rate plan. | |
| `RestrictionStatus Status="Close\ | Open" with Restriction="Master"` (or none) | Stop sell, or reopen. |
Restriction="Arrival" / "Departure" | Closed to arrival / departure. | |
LengthOfStay MinMaxMessageType="SetMinLOS" (or SetForwardMinStay, SetMinLOSOnArrival) | Minimum nights. Time="0" clears it. | |
MinMaxMessageType="SetMaxLOS" (or SetForwardMaxStay, SetMaxLOSOnArrival) | Maximum nights. Time="0" clears it. |
Which rate a restriction applies to. Without RatePlanCode, a restriction applies to the whole room: every rate is closed or limited. With a RatePlanCode (BASE included), it applies to that rate plan only. The room stays on sale with its other rates.
Availability only: OTA_HotelInvCountNotifRQ
<OTA_HotelInvCountNotifRQ xmlns="http://www.opentravel.org/OTA/2003/05" EchoToken="4">
<Inventories HotelCode="EJ-HTL-0002">
<Inventory>
<StatusApplicationControl Start="2026-11-01" End="2026-11-30" InvTypeCode="DLX"/>
<InvCounts><InvCount CountType="2" Count="5"/></InvCounts>
</Inventory>
</Inventories>
</OTA_HotelInvCountNotifRQ>We accept only CountType="2": rooms available to sell. Leaving CountType out means 2.
Rates: OTA_HotelRateAmountNotifRQ
<OTA_HotelRateAmountNotifRQ xmlns="http://www.opentravel.org/OTA/2003/05" EchoToken="5">
<RateAmountMessages HotelCode="EJ-HTL-0002">
<RateAmountMessage>
<StatusApplicationControl Start="2026-11-01" End="2026-11-30" InvTypeCode="DLX" RatePlanCode="CP"/>
<Rates>
<Rate CurrencyCode="INR">
<BaseByGuestAmts>
<BaseByGuestAmt AmountBeforeTax="3200" NumberOfGuests="1" AgeQualifyingCode="10"/>
<BaseByGuestAmt AmountBeforeTax="3700" NumberOfGuests="2" AgeQualifyingCode="10"/>
<BaseByGuestAmt AmountBeforeTax="4300" NumberOfGuests="3" AgeQualifyingCode="10"/>
</BaseByGuestAmts>
<AdditionalGuestAmounts>
<AdditionalGuestAmount AgeQualifyingCode="10" Amount="800"/>
<AdditionalGuestAmount AgeQualifyingCode="8" Amount="400"/>
</AdditionalGuestAmounts>
</Rate>
</Rates>
</RateAmountMessage>
</RateAmountMessages>
</OTA_HotelRateAmountNotifRQ>- Without
RatePlanCode, or withBASE, you set the room-only rate. With another code, you set that rate plan's price. - One price per night: a
BaseByGuestAmtwithoutNumberOfGuestsis the price at the room's standard occupancy (StandardOccupancyin the descriptive info). - A price per number of guests: give
NumberOfGuestswithAgeQualifyingCode="10"(adults). The price at standard occupancy is also used for bookings that don't say how many guests; if there isn't one, the nearest number below it is used.AgeQualifyingCode="8"withNumberOfGuestsprices that many children. - Extra guests:
AdditionalGuestAmountis the charge per night for each adult (10) above standard occupancy, or for each child (8). - A
Ratecan have its ownStart,Endand day flags, overriding theStatusApplicationControl's, to send several date ranges in one message. - Before tax. Send
AmountBeforeTax(orAmount). We add GST when the guest books, so a message with onlyAmountAfterTaxis refused. Amounts without a decimal point are divided by10^DecimalPlaceswhenDecimalPlacesis given (AmountBeforeTax="320000" DecimalPlaces="2"is 3200.00). - INR only. A
CurrencyCodeother thanINRis refused (error61).
Gross or net rates
The hotel says in its Channel Manager screen which rates you send:
- Gross (the default): the price the guest pays here. Our commission is deducted when we pay the hotel.
- Net: what the hotel should receive. We sell at that amount plus our commission: net ÷ (1 − commission rate).
Reservations use the same model. On a net connection, their room rates are net and their totals carry no AmountAfterTax.
4. Reservations
Every new booking, change and cancellation for the hotel reaches you as a HotelReservation. Its ResStatus is Book, Modify or Cancel, and UniqueID Type="14" carries our reference. The reservation is always a full snapshot, so store the latest one for each reference and replace older ones.
When the hotel goes live, every upcoming stay already sold on Experience Jharkhand (guests in-house and arrivals to come) reaches you as a Book. Count them before your next availability push. Going live needs a test reservation you have collected and confirmed, or taken at your webhook.
Test reservations say TEST RESERVATION in their comment and Test="true" in TPA_Extensions/ExperienceJharkhand. Don't give them to the hotel's front desk.
Option A: collect them (pull)
Ask for reservations not yet confirmed, oldest first, up to 50 at a time:
<OTA_ReadRQ xmlns="http://www.opentravel.org/OTA/2003/05" EchoToken="6">
<ReadRequests>
<HotelReadRequest HotelCode="EJ-HTL-0002">
<SelectionCriteria SelectionType="Undelivered"/>
</HotelReadRequest>
</ReadRequests>
</OTA_ReadRQ>The answer is an OTA_ResRetrieveRS with the reservations under ReservationsList. If more are waiting, it carries a warning; read again after you confirm these. One booking can appear more than once, for example as a Book and then a Modify. Apply them in the order given.
After storing them, confirm them:
<OTA_NotifReportRQ xmlns="http://www.opentravel.org/OTA/2003/05" EchoToken="7">
<Success/>
<NotifDetails>
<HotelNotifReport>
<HotelReservations>
<HotelReservation>
<UniqueID Type="14" ID="EJH2611-0042"/>
<ResGlobalInfo>
<HotelReservationIDs>
<HotelReservationID ResID_Type="10" ResID_Value="YOUR-CONFIRMATION-NUMBER"/>
</HotelReservationIDs>
</ResGlobalInfo>
</HotelReservation>
</HotelReservations>
</HotelNotifReport>
</NotifDetails>
</OTA_NotifReportRQ>- Confirming a reference confirms what you have read for it. A change that arrives between your read and your report isn't confirmed by the report. It is returned by your next read.
- Your own confirmation number (any
HotelReservationIDthat isn't ours) is shown to the hotel next to the booking. - If you couldn't store a reservation, send the report with
<Errors>instead of<Success/>. The reservation stays waiting, and the hotel sees your error. - Confirming the same reference again does no harm. A reference we don't know gets error
245.
Option B: we push them (OTA_HotelResNotifRQ)
If the hotel enters your URL as its reservation webhook, we POST each reservation there the moment it happens, as an OTA_HotelResNotifRQ with one HotelReservation. The root's ResStatus matches the reservation's, and its EchoToken is our event id. Pushed reservations are confirmed by your answer, so they don't also wait to be read.
- SOAP and WS-Security. If your endpoint needs them, the hotel turns on SOAP and enters the username and password you give it. We then send a SOAP 1.1 envelope with a WS-Security
UsernameToken(PasswordText) andSOAPAction: "OTA_HotelResNotifRQ". - Signature. Every push carries
X-EJ-Signature: t=<unix seconds>,v1=<hex>, the HMAC-SHA256 of"<t>.<exact body>"with the webhook signing secret the hotel was given, as the JSON API does. Check it, and refuse timestamps more than five minutes old. - Your answer: an
OTA_HotelResNotifRSwith<Success/>means it's delivered. Put your confirmation number in aHotelReservationIDand we'll show it to the hotel. An answer with<Errors>whose errors are allType="3"(business rule) orType="10"(required field missing) means you refuse the reservation as sent. We don't retry it, and the hotel is shown your error. Anything else, including other error types, a non-2xx status or no answer within 10 seconds, is retried with backoff: after 30 seconds, then doubling up to 6 hours, 15 times in all. - Reservations for a paused connection wait until the hotel resumes it.
What a reservation contains
| Where | What |
|---|---|
HotelReservation/@ResStatus | Book, Modify or Cancel. |
UniqueID Type="14" ID | Our reference, which is also in ResGlobalInfo/HotelReservationIDs with ResID_Source="Experience Jharkhand". |
RoomStay/RoomTypes/RoomType/@RoomTypeCode | Your code for the room if the hotel mapped one, else ours. NumberOfUnits is the number of rooms. |
RoomStay/RatePlans/RatePlan/@RatePlanCode | Your code for the rate plan if the hotel mapped one, else ours. BASE is room only. |
RoomRates/RoomRate/Rates/Rate | One per night: EffectiveDate, ExpireDate (the next day), and Base/@AmountBeforeTax for one room. |
GuestCounts | Adults (10) and children (8) per room. |
TimeSpan | Check-in (Start) and check-out (End). |
ResGuests/…/Customer | Guest name, phone, email and billing address. |
ResGlobalInfo/Total | AmountBeforeTax: the room total. AmountAfterTax (gross connections): what the guest pays, with Taxes. |
ResGlobalInfo/Comments | How payment works, in words: prepaid (don't charge the guest), advance (collect the balance) or pay at hotel. |
ResGlobalInfo/SpecialRequests | The guest's requests. |
TPA_Extensions/ExperienceJharkhand | EventID, EventType, Sequence and Test. Payment: Model (prepaid, advance, pay_at_hotel), Paid, CollectAtHotel. Settlement: commission, GST on commission, TCS, TDS and NetToHotel, as in the JSON API. |
5. Answers and errors
Answers are HTTP 200 with the matching …RS element. OTA_ReadRQ is answered with OTA_ResRetrieveRS. EchoToken, CorrelationID and TransactionIdentifier are echoed back. A success carries <Success/>, possibly with <Warnings> (for example: staged because the connection is testing). A failure carries <Errors>:
| Type | Code | Meaning |
|---|---|---|
| 4 | 497 | Unknown or revoked key. |
| 4 | 392 | The key isn't for this HotelCode. |
| 3 | 402 | Unknown room type (InvTypeCode). |
| 3 | 249 | Unknown or inactive rate plan for that room. |
| 3 | 15 | Invalid dates: unreadable, End before Start, or more than 731 days. |
| 3 | 320 | Invalid value (a negative amount, AmountAfterTax only, an unsupported restriction, …). |
| 3 | 61 | A currency other than INR. |
| 3 | 245 | Unknown reservation in OTA_NotifReportRQ. |
| 3 | 450 | Not accepted now: the connection is paused. |
| 10 | 321 | A required element or attribute is missing. |
| 2 | 450 | A message type we don't support. |
| 12 | 450 | Rate limit reached (HTTP 429, with Retry-After). |
| 12 | 448 | Our error: retry later. |
Limits
- Per key: 600 reads (
OTA_PingRQ,OTA_HotelDescriptiveInfoRQandOTA_ReadRQ) and 120 other messages a minute. Send many updates in one message rather than one message each. - Failed keys: 20 a minute per address, then HTTP
429. - Size: 5 MB per message. DTDs and entity declarations are refused.
6. Certification
Before connecting real hotels, certify on a sandbox hotel. Ask Experience Jharkhand for a sandbox key, open /developers/certification, and choose OpenTravel (OTA) XML. Each case then shows the exact OTA messages to send. The run checks what your messages did, and you finish by collecting, changing and cancelling a test reservation, by OTA_ReadRQ + OTA_NotifReportRQ or at your webhook. When every case passes, we review the run and mark you certified, and hotels see you as certified when they connect.