For channel managers · OpenTravel 2003B+

OpenTravel (OTA) XML connectivity

This guide is for channel managers that already connect hotels to online travel agencies with OpenTravel Alliance (OTA) XML messages, and want to connect them to Experience Jharkhand the same way. Experience Jharkhand is an online travel agency for Jharkhand, India.

It is an alternative to the JSON Connectivity API. Both give the same results, and a hotel's connection works with either: rates and availability go through the same checks, mappings and testing mode.

Two things flow through it:

  • In, from you: OTA_HotelAvailNotifRQ, OTA_HotelInvCountNotifRQ and OTA_HotelRateAmountNotifRQ for availability, rates and restrictions.
  • Out, to you: reservations as HotelReservations. You collect them with OTA_ReadRQ and confirm them with OTA_NotifReportRQ, or we push them to you as OTA_HotelResNotifRQ.

Amounts are in Indian rupees (INR), before tax. Dates are YYYY-MM-DD.

Endpointhttps://www.experiencejharkhand.in/api/channel/ota Markdown version

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:

ItemExample
Endpointhttps://www.experiencejharkhand.in/api/channel/ota
HotelCodeEJ-HTL-0002
Keyejck_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:

  1. The X-EJ-Channel-Key: <key> header, or Authorization: Bearer <key>.
  2. HTTP Basic authentication, with the key as the password. The username is ignored; the HotelCode is a good choice.
  3. A WS-Security UsernameToken in the SOAP header, with the key as the Password (PasswordText).
  4. 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"/>
  • BASE is 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 402 and 249), and it then appears in that screen for the hotel to match. Once it's matched, send the message again. The answer's TPA_Extensions/Mappings lists 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"/>
  • Start and End are 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 (or 1) are updated. Weds/Wed and Thur/Thu both work.
  • InvCode is accepted in place of InvTypeCode.

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 sendWhat 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 with BASE, you set the room-only rate. With another code, you set that rate plan's price.
  • One price per night: a BaseByGuestAmt without NumberOfGuests is the price at the room's standard occupancy (StandardOccupancy in the descriptive info).
  • A price per number of guests: give NumberOfGuests with AgeQualifyingCode="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" with NumberOfGuests prices that many children.
  • Extra guests: AdditionalGuestAmount is the charge per night for each adult (10) above standard occupancy, or for each child (8).
  • A Rate can have its own Start, End and day flags, overriding the StatusApplicationControl's, to send several date ranges in one message.
  • Before tax. Send AmountBeforeTax (or Amount). We add GST when the guest books, so a message with only AmountAfterTax is refused. Amounts without a decimal point are divided by 10^DecimalPlaces when DecimalPlaces is given (AmountBeforeTax="320000" DecimalPlaces="2" is 3200.00).
  • INR only. A CurrencyCode other than INR is refused (error 61).

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 HotelReservationID that 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) and SOAPAction: "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_HotelResNotifRS with <Success/> means it's delivered. Put your confirmation number in a HotelReservationID and we'll show it to the hotel. An answer with <Errors> whose errors are all Type="3" (business rule) or Type="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

WhereWhat
HotelReservation/@ResStatusBook, Modify or Cancel.
UniqueID Type="14" IDOur reference, which is also in ResGlobalInfo/HotelReservationIDs with ResID_Source="Experience Jharkhand".
RoomStay/RoomTypes/RoomType/@RoomTypeCodeYour code for the room if the hotel mapped one, else ours. NumberOfUnits is the number of rooms.
RoomStay/RatePlans/RatePlan/@RatePlanCodeYour code for the rate plan if the hotel mapped one, else ours. BASE is room only.
RoomRates/RoomRate/Rates/RateOne per night: EffectiveDate, ExpireDate (the next day), and Base/@AmountBeforeTax for one room.
GuestCountsAdults (10) and children (8) per room.
TimeSpanCheck-in (Start) and check-out (End).
ResGuests/…/CustomerGuest name, phone, email and billing address.
ResGlobalInfo/TotalAmountBeforeTax: the room total. AmountAfterTax (gross connections): what the guest pays, with Taxes.
ResGlobalInfo/CommentsHow payment works, in words: prepaid (don't charge the guest), advance (collect the balance) or pay at hotel.
ResGlobalInfo/SpecialRequestsThe guest's requests.
TPA_Extensions/ExperienceJharkhandEventID, 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>:

TypeCodeMeaning
4497Unknown or revoked key.
4392The key isn't for this HotelCode.
3402Unknown room type (InvTypeCode).
3249Unknown or inactive rate plan for that room.
315Invalid dates: unreadable, End before Start, or more than 731 days.
3320Invalid value (a negative amount, AmountAfterTax only, an unsupported restriction, …).
361A currency other than INR.
3245Unknown reservation in OTA_NotifReportRQ.
3450Not accepted now: the connection is paused.
10321A required element or attribute is missing.
2450A message type we don't support.
12450Rate limit reached (HTTP 429, with Retry-After).
12448Our error: retry later.

Limits

  • Per key: 600 reads (OTA_PingRQ, OTA_HotelDescriptiveInfoRQ and OTA_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.