For channel managers · Version 1

Experience Jharkhand Connectivity API v1

This guide is for channel managers adding Experience Jharkhand (an online travel agency for Jharkhand, India) as a channel for their hotels.

Two things flow through it:

  • In, from you: rates, availability and restrictions (ARI) for each room and rate plan, per date.
  • Out, to you: reservations: new ones, changes and cancellations. You can fetch them, receive them by webhook, or both.

All requests and responses are JSON over HTTPS. Amounts are in Indian rupees (INR). Dates are YYYY-MM-DD and times are UTC ISO 8601.

Already connect to OTAs with OpenTravel XML (OTA_HotelAvailNotifRQ, OTA_HotelRateAmountNotifRQ, OTA_HotelResNotifRQ, …)? You can use those messages instead, with the same key: see the OpenTravel guide.

Base URLhttps://www.experiencejharkhand.in/api/channel/v1 Markdown version

1. Getting connected

The hotel starts the connection. In its Experience Jharkhand vendor portal it opens Channel Manager, chooses you, and receives three things to send you:

ItemExample
API base URLhttps://www.experiencejharkhand.in/api/channel/v1
Hotel codeEJ-HTL-0002
API keyejck_1a2b3c4d_…

Send the key on every request in either header:

X-EJ-Channel-Key: ejck_1a2b3c4d_…
Authorization: Bearer ejck_1a2b3c4d_…
  • One key per hotel. A key only opens the hotel it was issued for. Using it on another hotel code returns 403.
  • Keep keys secret. They are shown to the hotel once. If a key leaks, the hotel can issue a new one, and the old key stops working at once (401).

Connection states

StateWhat happens
testingThe starting state. We check your ARI updates and store them, but we don't sell from them. The hotel compares them with its live calendar. Test reservations can be sent to you. Real ones are not.
liveARI is applied straight away. Every reservation for the hotel is sent to you. The hotel can no longer edit the connected rooms in Experience Jharkhand. You own their rates and availability.
pausedARI returns 409 and reservations are held. Resuming sends everything that is waiting.

When the hotel clicks Go live, the last values you sent during testing are applied. At the same moment, every upcoming stay already sold on Experience Jharkhand is sent to you as a reservation.new: guests in-house now and arrivals to come. Count them, then send a full refresh of at least the next 365 days with availability that takes them into account.

Go live is only possible once the hotel's go-live checklist passes. You have to have sent rates and availability for every connected room, with no ₹0 rate on an open night, and acknowledged a test reservation.

Check your key

GET/ping
{ "ok": true, "hotel_code": "EJ-HTL-0002", "hotel_name": "Hill View Resort",
  "connection": { "id": "…", "status": "testing", "provider": "ezee", "delivery": "pull" } }

2. Rooms, rate plans and mapping

GET/hotels/{hotel_code}
{
  "hotel_code": "EJ-HTL-0002",
  "name": "Hill View Resort",
  "currency": "INR",
  "rooms": [
    { "code": "DELUXE", "id": "6f0c…", "name": "Deluxe", "total_rooms": 8,
      "max_occupancy": 3, "max_adults": 2, "max_children": 1, "base_occupancy": 2,
      "base_rate": 2500 }
  ],
  "rate_plans": [
    { "code": "CP", "id": "a91e…", "name": "With breakfast", "meal_plan": "CP",
      "active": true, "rooms": ["DELUXE"] }
  ],
  "mappings": [ { "kind": "room", "external_code": "DLX", "target_id": "6f0c…" } ],
  "connection": { "status": "testing", "delivery": "pull" }
}
  • Room-only rate. Every room has a base rate with no rate plan (meal plan EP, room only). Rate plans are extra ways of selling a room, such as with breakfast (CP), breakfast and one meal (MAP) or all meals (AP).
  • Room and rate plan codes are stable. Renaming or reordering rooms doesn't change them.

There are two ways to refer to rooms and rate plans:

  1. Use our codes (DELUXE, CP) directly. No mapping is needed.
  2. Use your own codes. Send us your list, and the hotel matches each of your codes to one of its rooms or rate plans in the portal:
    PUT /hotels/{hotel_code}/products
    [
      { "kind": "room", "code": "DLX", "name": "Deluxe Double" },
      { "kind": "rate", "code": "BB",  "name": "Bed & Breakfast" },
      { "kind": "rate", "code": "RO",  "name": "Room Only" }
    ]

    Your codes then work anywhere a room or rate plan is named. A rate code the hotel maps to "Room only" refers to the base rate.

3. Sending rates, availability and restrictions

POST/hotels/{hotel_code}/ari
{
  "updates": [
    { "room": "DLX", "start": "2026-10-01", "end": "2026-10-31",
      "availability": 5, "rate": 2800 },
    { "room": "DLX", "rate_plan": "BB", "start": "2026-10-01", "end": "2026-10-31",
      "rate": 3200 },
    { "room": "DLX", "start": "2026-10-10", "end": "2026-10-12",
      "days": ["fri", "sat"], "min_stay": 2, "closed_to_arrival": false },
    { "room": "SUITE", "start": "2026-12-24", "end": "2026-12-26", "stop_sell": true }
  ]
}
FieldMeaning
roomRequired. Your mapped code or our room code.
rate_planA rate plan code. Leave it out to update the room-only rate.
start, endRequired. Both dates are included. At most 731 days per item. Dates before yesterday are ignored.
daysOnly update these weekdays: mon … sun.
availabilityRooms left to sell on Experience Jharkhand. This is an absolute number, not a change.
ratePrice per room per night, in INR. With rate_plan it sets that plan's price. Without it, it sets the room-only price.
extra_adult, extra_childAdded per night for each adult above the room's base occupancy (base_occupancy in GET /hotels/{code}), and for each child. Per rate plan like rate.
stop_selltrue closes the room to new bookings.
min_stay, max_stayNights, applied to stays that arrive on the date. 0 removes the limit.
closed_to_arrivalNo check-ins on the date.
closed_to_departureNo check-outs on the date.
  • What each field applies to: availability, stop_sell and restrictions are set per room, even when rate_plan is given. Only rate is set per rate plan.
  • Unchanged fields: a field you leave out is not changed.
  • Request size: send up to 1,000 items per request.

Response:

{ "success": false, "mode": "applied", "received": 4, "accepted": 3, "nights": 66,
  "errors": [ { "index": 3, "room": "SUITE", "error": "Unknown room 'SUITE' — map it …" } ] }
  • mode is staged while testing and applied once live.
  • Partial success: the valid items are processed even when others fail. Check errors. An item fails for an unknown room or rate plan, a bad date range, or no fields to update.
  • Status codes: 400 means the whole request is malformed. 409 means the connection is paused.

Gross or net rates

By default a connection sends gross rates: the price the guest pays, from which we deduct our commission. A hotel can switch its connection to net (Channel Manager → Settings): you then send what the hotel should receive, and we sell at that plus our commission: selling rate = net ÷ (1 − commission rate), e.g. 8000 net at 20% sells at

  1. This applies to rate and the occupancy prices; GET /ari returns net rates on a net connection, and GET /hotels/{code} shows connection.rate_model. After a switch, send a full refresh.

Reading back what we sell

GET/hotels/{hotel_code}/ari?start=2026-10-01&end=2026-10-31

Returns one row per room and date: availability, rate, plan_rates (keyed by our plan code), stop_sell, min_stay, max_stay, closed_to_arrival and closed_to_departure. At most 366 days per request. Use it to reconcile.

4. Receiving reservations

Every Experience Jharkhand booking for the hotel becomes one or more events:

  • reservation.new: paid online, or created at the hotel's front desk in our portal.
  • reservation.modified: dates, room, number of rooms, or guest details changed.
  • reservation.cancelled: the booking was cancelled.

Each event carries the whole reservation as it stands, so you can replace your copy with it. Events have a sequence that increases for each connection. Apply them in order, and ignore any event with a sequence lower than one you have already applied for that reservation.

{
  "event_id": "5b0e…",
  "event_type": "reservation.new",
  "sequence": 42,
  "test": false,
  "hotel_code": "EJ-HTL-0002",
  "reservation": {
    "id": "c1d2…",
    "reference": "EJH-2610-0042",
    "status": "confirmed",
    "source": "experience_jharkhand",
    "check_in": "2026-10-10",
    "check_out": "2026-10-12",
    "nights": 2,
    "rooms": [{
      "room_code": "DELUXE", "external_room_code": "DLX", "room_name": "Deluxe",
      "rate_plan_code": "CP", "external_rate_code": "BB", "meal_plan": "CP",
      "quantity": 1, "guests": 2, "adults": 2, "children": 0, "extra_beds": 0,
      "daily_rates": [ { "date": "2026-10-10", "rate": 3200 },
                       { "date": "2026-10-11", "rate": 3200 } ]
    }],
    "guest": { "name": "Asha Kumari", "email": "asha@example.com", "phone": "+919000000000" },
    "amount": { "currency": "INR", "total": 6720, "tax": 320, "commission": 960 },
    "payment": { "model": "prepaid", "status": "paid", "paid": 6720, "collect_at_hotel": 0 },
    "settlement": { "rate_model": "gross", "commission_rate": 15, "commission": 960,
                    "gst_on_commission": 172.8, "tcs": 0, "tds": 6.4,
                    "hotel_earnings": 5580.8, "net_to_hotel": 5580.8 },
    "special_requests": "Late check-in",
    "created_at": "2026-09-25T10:15:00+00:00",
    "modified_at": "2026-09-25T10:15:00+00:00"
  }
}

Field notes

  • source: experience_jharkhand for online bookings. walk_in for bookings the hotel made at its own front desk in our portal; these still take up a room.
  • payment.model (each hotel's is set by Experience Jharkhand):
    • prepaid: the guest paid Experience Jharkhand in full, which settles with the hotel. Don't charge the guest.
    • advance: the guest paid part online (paid). Collect collect_at_hotel at the hotel.
    • pay_at_hotel: nothing was paid online. Collect collect_at_hotel (the full amount) at the hotel.
    • hotel_collect: a booking the hotel entered itself; it collects payment.
  • settlement: what the booking comes to between Experience Jharkhand and the hotel. Commission is a percentage of the amount before GST, with 18% GST on it; tcs and tds are taxes we withhold. net_to_hotel is what we pay the hotel for this booking; a negative value is what the hotel owes us (it collected the money, e.g. pay at hotel). The monthly totals are in GET /hotels/{code}/statements.
  • daily_rates[].net_rate: on connections that use net rates (below), each night's rate less our commission, alongside the guest's rate.
  • external_room_code / external_rate_code: your mapped codes, or null when you use our codes. rate_plan_code is null for the room-only rate.
  • test: true: the hotel sent it from the portal to test the connection. Acknowledge it, but never create a real booking from it.

Option A: fetch them (pull)

GET/hotels/{hotel_code}/reservations?limit=50
{ "hotel_code": "EJ-HTL-0002", "events": [ … ], "has_more": false }
  • What's returned: events you haven't acknowledged, oldest first. limit can be up to 200.
  • Other parameters: include_acknowledged=true includes events you've already acknowledged. after_sequence=N resumes after sequence N.
  • Acknowledge each event once it is stored. An event you don't acknowledge is returned again.
POST /hotels/{hotel_code}/reservations/ack
{ "event_ids": ["5b0e…", "7c1f…"] }

Poll every 1–5 minutes.

Option B: webhook (push)

If you give the hotel an HTTPS URL, we POST each event to it as it happens.

  • Headers sent with each event:
    Content-Type: application/json
    X-EJ-Event-Id: 5b0e…
    X-EJ-Event-Type: reservation.new
    X-EJ-Signature: t=1790000000,v1=5d41402abc4b2a76b9719d911017c592…
  • Verifying the signature: compute HMAC-SHA256 over "{t}.{raw body}" using the signing secret the hotel gives you (whsec_…). Compare it with v1, and reject events whose t is more than 5 minutes old.
  • Replying: reply 2xx within 10 seconds to acknowledge the event.
  • Retries: anything else is retried with exponential backoff (30 s, doubling, capped at 6 h) for about a day. After that the event is marked failed for the hotel to retry.
  • Order: events can occasionally arrive out of order after a retry. Use sequence.
  • Duplicates: after a timeout the same event can arrive twice. Use event_id.

The pull endpoint also returns any webhook event that hasn't been acknowledged yet. You can use both.

import hashlib, hmac, time

def verify(secret: str, header: str, body: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    signed = f"{parts['t']}.".encode() + body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"]) and abs(time.time() - int(parts["t"])) < 300

Rate parity

Tell us what you sell the hotel's rooms for on its other channels, and we'll alert the hotel when it's cheaper there than here:

POST/api/channel/v1/hotels/{code}/parity
{ "observations": [
  { "channel": "Booking.com", "room": "DLX", "rate_plan": "CP", "date": "2026-10-10", "rate": 4800 }
] }
  • rate is the guest price per night before tax, as our rates are.
  • room and rate_plan take your mapped codes or ours.
  • Send today's and future nights, up to 1000 observations per request.
  • A newer observation replaces the older one for the same room, plan, night and channel. Observations older than 7 days are ignored.
  • The response is { "accepted": n, "errors": [{ "index", "error" }] }.

We compare each night with our current rate. When ours is more than 1% higher on a night still on sale, the hotel gets an alert and sees the difference in its Reports.

Statements

GET/api/channel/v1/hotels/{code}/statements?month=2026-10

Every Experience Jharkhand booking that checked out in the month, each with the settlement fields above, plus totals and the payouts made to the hotel that month. Cancelled bookings are listed at zero; unpaid ones and the hotel's own walk-ins are left out. The hotel sees the same statement, and can download it as CSV, in its portal.

5. Errors

Errors return {"detail": "…"} with one of these status codes:

CodeMeaning
400The request is malformed, or the dates or values are invalid.
401The key is missing, wrong, rotated or revoked.
403The key is valid but for another hotel.
404Unknown hotel code.
409The connection is paused.
413The body is over 5 MB. Split the push.
429Too many requests. Wait for the seconds in the Retry-After header, then retry.
5xxOur side. Retry with backoff.

Limits, per key and per minute:

  • 600 reads (GET);
  • 120 writes: ARI pushes, acknowledgements, product and parity uploads.

Each ARI push can carry up to 1000 date ranges, so batch updates rather than sending one request per room and date. Repeated requests with a missing or wrong key from one address are blocked for a minute.

6. Certification

Certify your integration once, on a sandbox hotel, before you connect real hotels. Hotels see certified channel managers marked as such when they pick theirs.

  1. Get a sandbox. Ask Experience Jharkhand for one (hello@experiencejharkhand.in). You get a sandbox hotel code (EJ-SBX-…) and a key for it. The hotel has two rooms (DLX Deluxe Room, STE Forest Suite) and one rate plan (CP, with breakfast). It is never listed or bookable, and what you send stays staged: nothing is sold from it.
  2. Start a run at experiencejharkhand.in/developers/certification with your key, or with POST /certification/runs. A run fixes its test dates when it starts and gives each case an example request with the real dates and codes. Starting a new run clears what the sandbox has received.
  3. Work through the cases:
    • read the rooms and rate plans (GET /hotels/{code});
    • a full refresh: availability for both rooms for the next 90 days;
    • availability, a room-only rate and a rate plan price for a date range;
    • a Friday-and-Saturday rate that leaves the other days alone (days);
    • stop sell, and a date explicitly reopened (stop_sell: false);
    • a minimum stay, closed to arrival and closed to departure;
    • an extra-adult price;
    • an update we reject (room NOT-A-ROOM), which your system should show the hotel;
    • receive and acknowledge a test reservation, then its modification, then its cancellation. Each is sent once the one before it is acknowledged, by pull and ack or at your webhook. To use a webhook, set it on the certification page or with PUT /certification/webhook ({"url": "https://…"}; "" goes back to pulling). The first one returns its signing secret.
    • (optional) your own room and rate codes with PUT /products.
  4. Check progress on the page or with GET /certification. Cases are checked every time you look, against what your system actually sent. Once a case passes it stays passed for that run.
  5. When every required case passes, we review the run and certify you.
GET /api/channel/v1/certification
X-EJ-Channel-Key: ejck_…
{
  "partner": { "name": "Acme Channel", "status": "sandbox" },
  "hotel_code": "EJ-SBX-3F9A1C",
  "run": {
    "status": "running",
    "passed_count": 9,
    "required_count": 15,
    "cases": [
      { "id": "availability", "title": "Availability", "required": true, "status": "passed",
        "instructions": "Set Deluxe Room (DLX) availability to 4 from … to ….",
        "request": { "method": "POST /api/channel/v1/hotels/EJ-SBX-3F9A1C/ari", "body": { "updates": ["…"] } },
        "detail": "Received as expected" }
    ]
  }
}

Going live with a hotel

Each hotel still starts in testing. Before it goes live, check with the hotel:

  1. Every room and rate plan is mapped (see GET /hotels/{code}).
  2. A full ARI push returns no errors, and the hotel sees your values in Compare & go live.
  3. A test reservation sent from its portal reaches you and is acknowledged.
  4. At go-live, the hotel's upcoming stays arrive as reservation.new events. After that, a real booking made on Experience Jharkhand reaches you, and your next availability push reflects them all.

Contact Experience Jharkhand partner support with the hotel code for help.