rovitravel Connectivity API v1
This document describes the full partner-facing integration contract for hotel distribution, from static catalog synchronization to real-time availability, precheck validation, booking creation, booking retrieval, and cancellation. It is generated from real implementation logic in the service solution (controllers, command/query handlers, DTOs) so partners can integrate against behavior that matches production processing.
The API is designed for high-throughput OTA and B2B partner systems that need a stable JSON contract, deterministic booking flow, and explicit error handling. Recommended implementation order is: static full sync, scheduled delta sync, runtime availability, mandatory precheck, booking submit, then post-booking status and cancellation operations.
Authentication
Authorization: Bearer <jwt-token> Content-Type: application/json
All endpoints below are protected by [Authorize].
Integration Journey — End to End
Read top to bottom like a checklist. Each step shows what it means in plain language (for PM / ops / business) and the exact API call (for engineers). Follow the phases in order when you go live.
Download the hotel catalog (first time)
Giống như tải “danh bạ khách sạn” về máy bạn: tên, địa chỉ, ảnh, loại phòng — chưa có giá theo ngày.
Get hotel list (paginated)
Pull hotels in batches until hasMore=false. Save the full property record from each item in result.properties[] — not only hotelId (name, address, geo, images, facilities, updatedDateTS, etc.).
API: GET /api/v1/partner/static/properties?afterHotelId=&pageSize=50 — persist entire PropertySummaryDto per row; use nextAfterHotelId for the next page.
Get room types per hotel
For each saved hotelId, download and store the full room list (names, capacity, photos, facilities) — still static, not live price.
API: GET /api/v1/partner/static/rooms/{propertyId} — persist all fields in each room object
Store in partner database
Partner website/app searches this local catalog. Users only see hotels the partner has synced.
Partner side: persist properties + rooms; keep updatedDateTS watermark
Keep catalog fresh (scheduled job)
Chỉ cập nhật khách sạn có thay đổi — không cần tải lại toàn bộ mỗi ngày.
Ask “what changed since last time?”
You get a list of hotel IDs that were updated. Save the new timestamp for next run.
API: GET /api/v1/partner/static/delta?updatedDateTs={watermark}
Refresh only those hotels
One API call per changed hotelId — replace the full property record and re-sync rooms (same fields as initial full sync).
API: GET /api/v1/partner/static/properties/{propertyId} (loop each id)
Search price → confirm → pay (real-time)
Khi khách chọn ngày đi / về: hỏi giá thật, khóa giá trước khi thanh toán, rồi tạo booking. Không được bỏ bước Precheck.
Search live price & rooms
Customer picks dates → you show available rooms and total price. User selects one offer.
API: POST /api/v1/partner/availability → save roomId, rateId, finalPrice
Precheck — lock the offer (~25 min)
“Hold” this price with rovitravel before payment. Partner receives tokens needed to book.
API: POST /api/v1/partner/precheck → precheckGuid, searchId (string)
Create booking
After customer pays, send guest names. Same precheck tokens required or booking fails.
API: POST /api/v1/partner/bookings (guests + precheckGuid + rateId + searchId)
Track status & cancel if needed
Theo dõi trạng thái đặt phòng; hủy khi chính sách cho phép.
Check booking status
Poll until status is final (e.g. confirmed, completed, or cancelled).
API: POST /api/v1/partner/bookings-list
Cancel (optional)
Only if cancellation policy allows; may incur penalty.
API: POST /api/v1/partner/bookings/{bookingGuid}/cancel
Monitor partner credit (anytime)
Song song với vận hành — hiển thị hạn mức còn lại trên admin partner; không bắt buộc trước static sync.
Check remaining credit
Poll for dashboard: available balance, debt, alert level. Bookings auto-check credit on POST /bookings.
API: GET /api/v1/partner/balance?keywords= (optional)
Who Does What? — System View
Two systems talk to each other. Arrows mean “request / response”. The partner builds and runs the left box; rovitravel hosts the API on the right. All hotel inventory and booking logic behind the API is handled by rovitravel — partners do not integrate elsewhere.
JWT + JSON
Non-tech: partner only connects to rovitravel API. Tech: send Bearer JWT on every call; rovitravel processes catalog, pricing, and booking internally.
Booking moment — read each row left → right
Show search form
End user enters hotel, dates, number of guests.
POST /availability
Quote live inventory
Checks real-time stock and price; returns room list, total price, and rateId.
Handled inside rovitravel API
User confirms offer
Partner sends the same price shown on screen.
POST /precheck
Lock ~25 minutes
Re-validates offer; returns precheckGuid + searchId for booking.
Precheck + rate hold (server-side)
Send guest names
Main contact phone + email required.
POST /bookings
Create booking
Checks partner credit; confirms with hotel; returns bookingGuid and status.
Booking create + confirm (server-side)
Track or cancel
Partner customer service checks status or requests cancel.
bookings-list · …/cancel
Read / cancel
Returns updated status; applies cancellation policy when allowed.
Status read + cancel (server-side)
API Map
/api/v1/partner/static/propertiesPaginated full property rows — save entire object per hotel, not id only
/api/v1/partner/static/properties/{propertyId}Single property detail (hotelId)
/api/v1/partner/static/rooms/{propertyId}Static room metadata
/api/v1/partner/static/deltaChanged hotel IDs from watermark
/api/v1/partner/availabilitySearch or browse available room offers (hotel-level request)
/api/v1/partner/precheckValidate chosen offer and lock booking context
/api/v1/partner/bookingsCreate booking from precheck result
/api/v1/partner/bookings-listRead multiple booking statuses
/api/v1/partner/bookings/{bookingGuid}/cancelCancel booking based on provider policy
/api/v1/partner/balanceCredit / debt snapshot for partner wallet
Availability
POST/api/v1/partner/availability
Hotel-level search input. Per your latest spec, no roomId / rateId / inventoryProvider in this request sample.
Compatibility note: top-level result.summaryFinalPrice and result.summaryAvailableRooms are summary values of the cheapest room in result.rooms[].
{
"hotelId": 12345,
"checkIn": "2026-06-01",
"checkOut": "2026-06-03",
"rooms": 1,
"adults": 2,
"children": 0,
"childrenAges": [],
"currency": "VND",
"userCountry": "VN"
}
Example Response (Browse Mode)
{
"isOK": true,
"statusCode": 200,
"result": {
"hotelId": 12345,
"rooms": [
{
"roomId": "6789",
"rateId": "r_9x7k2m1p",
"roomName": "Deluxe Double",
"finalPrice": 1290000,
"availableRooms": 2,
"dailyRates": [
{ "stayDate": "2026-06-01", "price": 645000, "boardType": "RO", "currency": "VND" }
],
"cancellationPolicy": {
"tiers": [
{ "penaltyPercent": 0, "cancellationNights": 0, "cutoffDate": "2026-05-30" },
{ "penaltyPercent": 100, "cancellationNights": 1, "cutoffDate": "2026-05-31" }
]
}
}
],
"summaryFinalPrice": 1290000,
"summaryAvailableRooms": 2
},
"errorMessages": []
}
Field Notes (easy to confuse)
hotelId: numeric hotel key in your static catalog.rooms(request): number of units to book;rooms(response): list of offers.finalPrice: total partner-facing price for selected offer (not per-night unless split bydailyRates).rateIdappears in response only; you pass it to/precheck.rateIdis an opaque token string. Treat it as-is; do not parse by prefix/format.
Precheck
POST/api/v1/partner/precheck
{
"hotelId": 12345,
"roomId": "6789",
"rateId": "r_9x7k2m1p",
"finalPrice": 1290000,
"checkIn": "2026-06-01",
"checkOut": "2026-06-03",
"rooms": 1,
"adults": 2,
"children": 0,
"childrenAges": [],
"currency": "VND",
"userCountry": "VN"
}
{
"precheckGuid": "9e803458-4a5e-4064-b2dd-95e8a7c6bd3e",
"hotelId": 12345,
"roomId": "6789",
"rateId": "r_9x7k2m1p",
"searchId": "752392028613223740",
"finalPrice": 1290000,
"availableRooms": 2,
"confirmed": true
}
Field Notes
precheckGuid: short-lived booking token; required by/bookings.searchId: must be preserved as a string; send unchanged to/bookings.confirmed=true: server has revalidated price+inventory for this exact offer.
Bookings
POST/api/v1/partner/bookings
{
"clientReferenceId": "partner-uuid-v4",
"precheckGuid": "9e803458-4a5e-4064-b2dd-95e8a7c6bd3e",
"rateId": "r_9x7k2m1p",
"searchId": "752392028613223740",
"guests": [
{
"isMainContact": true,
"fullName": "Nguyen Van A",
"nationalityIso2": "VN",
"phoneNumber": "0909123456",
"email": "[email protected]"
}
]
}
Example Response
{
"isOK": true,
"statusCode": 201,
"result": {
"bookingId": "987654",
"bookingGuid": "a6f11ba8-7dc7-4477-a1ef-37c5641b468a",
"status": "AreBooking",
"rooms": 1,
"finalPrice": 1290000
},
"errorMessages": []
}
Important: searchId must be JSON string (not number).
POST/api/v1/partner/bookings-list
{
"bookingGuids": [
"a6f11ba8-7dc7-4477-a1ef-37c5641b468a",
"b6f11ba8-7dc7-4477-a1ef-37c5641b4699"
]
}
{
"isOK": true,
"statusCode": 200,
"result": [
{
"bookingId": "987654",
"status": "Completed",
"rooms": 1,
"finalPrice": 1290000,
"bookingGuid": "a6f11ba8-7dc7-4477-a1ef-37c5641b468a"
},
{
"bookingId": "987655",
"status": "AreBooking",
"rooms": 1,
"finalPrice": 1410000,
"bookingGuid": "b6f11ba8-7dc7-4477-a1ef-37c5641b4699"
}
],
"errorMessages": []
}
POST/api/v1/partner/bookings/{bookingGuid}/cancel
{
"cancelReason": 0,
"cancelReasonText": "Guest changed plan"
}
{
"isOK": true,
"statusCode": 200,
"result": {
"bookingId": "987654",
"status": "Cancelled",
"rooms": 1,
"finalPrice": 1290000,
"bookingGuid": "a6f11ba8-7dc7-4477-a1ef-37c5641b468a"
},
"errorMessages": []
}
Static Catalog and Delta
GET/api/v1/partner/static/properties
Use this for full catalog sync. Cursor mode by afterHotelId. Each element in result.properties[] is a complete PropertySummaryDto — store all returned fields in the partner database (do not extract and save only hotelId).
Query params: - afterHotelId: int? (optional, start point; fetch records with hotelId > this value) - pageSize: int (optional, default 50, max 100)
{
"isOK": true,
"statusCode": 200,
"result": {
"properties": [
{
"hotelId": 12345,
"name": "Hotel A",
"translatedName": "Khach san A",
"hotelFormerlyName": null,
"remark": "Near city center",
"overview": "Business and leisure hotel",
"cityName": "Ho Chi Minh",
"cityTranslated": "TP. Ho Chi Minh",
"countryName": "Vietnam",
"countryTranslated": "Viet Nam",
"areaName": "District 1",
"starRating": 4,
"ratingAverage": 8.6,
"numberOfReviews": 1532,
"latitude": 10.7769,
"longitude": 106.7009,
"checkInTime": "14:00",
"checkOutTime": "12:00",
"accommodationType": "Hotel",
"facilities": ["WiFi", "Pool", "Gym"],
"status": "Active",
"updatedDateTS": 1779255738,
"addressShort": "District 1, Ho Chi Minh City",
"images": [
{ "pictureId": 1, "url": "https://cdn.example/hotel-12345-1.jpg", "caption": "Facade" }
]
}
],
"pageSize": 50,
"hasMore": true,
"nextAfterHotelId": 12345,
"totalItems": 50
},
"errorMessages": []
}
GET/api/v1/partner/static/properties/{propertyId}
One hotel per request. After delta sync, call this once per id in changedHotelIds.
Example: - /api/v1/partner/static/properties/12345
{
"isOK": true,
"statusCode": 200,
"result": {
"hotelId": 12345,
"name": "Hotel A",
"cityName": "Ho Chi Minh",
"starRating": 4,
"updatedDateTS": 1779255738
},
"errorMessages": []
}
GET/api/v1/partner/static/rooms/{propertyId}
{
"isOK": true,
"statusCode": 200,
"result": [
{
"roomId": "6789",
"hotelRoomtypeId": 6789,
"name": "Deluxe Double",
"nameTranslated": "Phong Deluxe doi",
"maxOccupancy": 2,
"roomInventory": 20,
"sizeSqm": 32,
"roomSizeIncludesTerrace": false,
"views": "City view",
"maxExtraBeds": 1,
"maxInfantsPerRoom": 1,
"bedType": "Double",
"bedTypeTranslated": "Giuong doi",
"primaryImageUrl": "https://cdn.example/room-6789-main.jpg",
"imageUrls": [
"https://cdn.example/room-6789-main.jpg",
"https://cdn.example/room-6789-2.jpg"
],
"alternateName": null,
"sharedBathroom": false,
"floor": "12",
"genderRestriction": null,
"facilities": [
{ "propertyId": 12345, "name": "Air conditioning", "nameTranslated": "May lanh" }
]
}
],
"errorMessages": []
}
GET/api/v1/partner/static/delta
Use this after initial sync. Returns only changed hotel IDs since your last watermark.
Query params: - updatedDateTs: long (Unix seconds UTC; recommended) - lastUpdated: string (legacy fallback)
{
"isOK": true,
"statusCode": 200,
"result": {
"changedHotelIds": [12345, 12388, 12601],
"nextUpdatedDateTs": 1779259999,
"hasMore": false
},
"errorMessages": []
}
What to persist from /static/properties
Minimum fields partners should store per hotel (same schema as list and detail):
hotelId— primary key for later availability/booking- Names & copy:
name,translatedName,overview,remark - Location:
cityName,countryName,areaName,addressShort,latitude,longitude - Hotel facts:
starRating,ratingAverage,checkInTime,checkOutTime,accommodationType,facilities[],status - Media: full
images[](url, caption, pictureId) - Sync:
updatedDateTS— required for delta watermark logic
How To Sync (recommended)
- Run full sync via
/static/propertiesuntilhasMore=false; upsert every object inproperties[]with all fields above. - For each saved
hotelId, load and persist full room metadata via/static/rooms/{propertyId}. - Store
nextUpdatedDateTswatermark. - Schedule delta sync: call
/static/delta?updatedDateTs=<watermark>. - Refresh each changed hotel via
/static/properties/{propertyId}(one HTTP call per hotel id). - Update local watermark with returned
nextUpdatedDateTs.
Rules and Limits
pageSizemax is 100 (server clamps out-of-range values).- Delta lookback max 7 days. Beyond that, API returns
DELTA_WINDOW_EXCEEDED; run full sync again. - If
updatedDateTsinvalid format, API returnsINVALID_UPDATED_DATE_TS. - When
hasMore=trueon delta, continue immediately using returnednextUpdatedDateTs.
Partner balance (credit / công nợ)
Partners on credit (MEMBER wallet) can check remaining limit before selling rooms. Balance is read through rovitravel API (proxy to internal balance-service) — partners do not call balance-service directly.
When it matters:
- Dashboard — poll
GET /balanceto show “remaining credit” in partner admin UI. - Before book —
POST /bookingscomparesprecheck.finalPrice+ server buffer %; returnsINSUFFICIENT_BALANCE(402) if not enough.
Alert levels
| alertLevel | Meaning (plain) | Typical action |
|---|---|---|
Ok | Enough credit for normal operations | No action |
Warning | Available < 20% of credit limit | Show banner; consider top-up |
Critical | Available < 5% of limit, or zero without limit | Urgent top-up |
Blocked | Not enough for the booking amount (+ buffer) | POST /bookings rejected with 402 |
GET/api/v1/partner/balance
Returns a snapshot of partner credit. Response is cached server-side (default ~120s) — safe to poll every few minutes, not every second.
Query params: - keywords: string (optional) Default: partner identity from JWT (username / guid). Use when your balance account keyword differs from JWT identity.
{
"isOK": true,
"statusCode": 200,
"result": {
"partnerKeyword": "OTA_PARTNER_A",
"availableBalance": 45000000,
"creditLimit": 100000000,
"currentDebt": 55000000,
"currency": "VND",
"alertLevel": "Warning",
"message": "Credit running low: 45,000,000 VND remaining."
},
"errorMessages": []
}
Field notes — save / display all of these
partnerKeyword— wallet key used for the lookup (echo of request keyword).availableBalance— số dư còn dùng được để đặt phòng mới.creditLimit— hạn mức tín dụng tổng (nếu có).currentDebt— dư nợ / phần đã dùng.currency— thườngVND.alertLevel—Ok|Warning|Critical|Blocked(enum string in JSON).message— human-readable hint; may benullwhenOk.
Booking rule: server requires availableBalance ≥ finalPrice × (1 + buffer%) on POST /bookings (buffer configured by rovitravel, e.g. 5%).
Recommended usage
- Show
GET /balanceon partner finance / admin page (refresh every 2–5 minutes). - Surface
alertLevelandmessageas UI banners when notOk. - On
INSUFFICIENT_BALANCEfromPOST /bookings, direct user to top-up — do not retry without increasing credit.
Deprecated Endpoints
POST /api/v1/partner/recheck returns RECHECK_DEPRECATED. Use POST /api/v1/partner/precheck instead.
Error Codes
| Code | HTTP | Description |
|---|---|---|
INVALID_PROPERTY | 400 | hotelId/propertyId missing or invalid. |
PRICE_CHANGED | 409 | Price changed; rerun availability and precheck. |
NO_INVENTORY | 409 | No rooms left for selected offer. |
RATE_NOT_FOUND | 409 | Selected rate token no longer valid. |
RATE_EXPIRED | 410/502 | Cached snapshot missing/expired. |
PRECHECK_INVALID | 400 | Missing/expired/invalid precheck. |
PRECHECK_SEARCH_ID_MISMATCH | 400 | searchId must match precheck and be string. |
BOOKING_NOT_FOUND | 404 | Booking guid not found. |
NONREFUND | 403 | Non-refundable cancellation blocked. |
INSUFFICIENT_BALANCE | 402 | Not enough credit for booking (after buffer). Top up account. |
BALANCE_SERVICE_NOT_CONFIGURED | 503 | Balance service endpoint missing on gateway. |
INVALID_UPDATED_DATE_TS | 400 | Delta watermark format invalid. |
DELTA_WINDOW_EXCEEDED | 400 | Delta lookback > 7 days. |