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:
| Item | Example |
|---|---|
| API base URL | https://www.experiencejharkhand.in/api/channel/v1 |
| Hotel code | EJ-HTL-0002 |
| API key | ejck_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
| State | What happens |
|---|---|
testing | The 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. |
live | ARI 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. |
paused | ARI 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
{ "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
{
"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:
- Use our codes (
DELUXE,CP) directly. No mapping is needed. - 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
{
"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 }
]
}| Field | Meaning |
|---|---|
room | Required. Your mapped code or our room code. |
rate_plan | A rate plan code. Leave it out to update the room-only rate. |
start, end | Required. Both dates are included. At most 731 days per item. Dates before yesterday are ignored. |
days | Only update these weekdays: mon … sun. |
availability | Rooms left to sell on Experience Jharkhand. This is an absolute number, not a change. |
rate | Price 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_child | Added 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_sell | true closes the room to new bookings. |
min_stay, max_stay | Nights, applied to stays that arrive on the date. 0 removes the limit. |
closed_to_arrival | No check-ins on the date. |
closed_to_departure | No check-outs on the date. |
- What each field applies to: availability,
stop_selland restrictions are set per room, even whenrate_planis given. Onlyrateis 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 …" } ] }modeisstagedwhile testing andappliedonce 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:
400means the whole request is malformed.409means 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
- This applies to
rateand the occupancy prices;GET /arireturns net rates on a net connection, andGET /hotels/{code}showsconnection.rate_model. After a switch, send a full refresh.
Reading back what we sell
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_jharkhandfor online bookings.walk_infor 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). Collectcollect_at_hotelat the hotel.pay_at_hotel: nothing was paid online. Collectcollect_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;tcsandtdsare taxes we withhold.net_to_hotelis 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 inGET /hotels/{code}/statements.daily_rates[].net_rate: on connections that use net rates (below), each night's rate less our commission, alongside the guest'srate.external_room_code/external_rate_code: your mapped codes, ornullwhen you use our codes.rate_plan_codeisnullfor 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)
{ "hotel_code": "EJ-HTL-0002", "events": [ … ], "has_more": false }- What's returned: events you haven't acknowledged, oldest first.
limitcan be up to 200. - Other parameters:
include_acknowledged=trueincludes events you've already acknowledged.after_sequence=Nresumes 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 withv1, and reject events whosetis more than 5 minutes old. - Replying: reply
2xxwithin 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"])) < 300Rate 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:
{ "observations": [
{ "channel": "Booking.com", "room": "DLX", "rate_plan": "CP", "date": "2026-10-10", "rate": 4800 }
] }rateis the guest price per night before tax, as our rates are.roomandrate_plantake 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
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:
| Code | Meaning |
|---|---|
| 400 | The request is malformed, or the dates or values are invalid. |
| 401 | The key is missing, wrong, rotated or revoked. |
| 403 | The key is valid but for another hotel. |
| 404 | Unknown hotel code. |
| 409 | The connection is paused. |
| 413 | The body is over 5 MB. Split the push. |
| 429 | Too many requests. Wait for the seconds in the Retry-After header, then retry. |
| 5xx | Our 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.
- 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 (DLXDeluxe Room,STEForest 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. - 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. - 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.
- read the rooms and rate plans (
- 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. - 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:
- Every room and rate plan is mapped (see
GET /hotels/{code}). - A full ARI push returns no
errors, and the hotel sees your values in Compare & go live. - A test reservation sent from its portal reaches you and is acknowledged.
- At go-live, the hotel's upcoming stays arrive as
reservation.newevents. 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.