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.

Doc: https://api-out.rovitravel.com Base URL: /api/v1/partner JSON UTF-8 HTTPS Bearer JWT MethodResult envelope Balance / credit APIs

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.

Phase 1 · Setup

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.

1
🏨

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.

2
🛏️

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

3
💾

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

Phase 2 · Daily updates

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.

4
🔔

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}

5
🔄

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)

↓ when a customer searches & books ↓
Phase 3 · Live booking

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.

6
🔍

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

7

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)

8
📝

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)

Phase 4 · After booking

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.

9
📊

Check booking status

Poll until status is final (e.g. confirmed, completed, or cancelled).

API: POST /api/v1/partner/bookings-list

10

Cancel (optional)

Only if cancellation policy allows; may incur penalty.

API: POST /api/v1/partner/bookings/{bookingGuid}/cancel

Optional · Credit

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)

Plain text = anyone can follow API line = developers implement

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.

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

① Partner system
② rovitravel API
When customer searches

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

Before payment — required

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)

After customer pays

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)

Later — support & cancel

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)

Read each row left → right like a story Bottom line on each card = implementation hint

API Map

GET/api/v1/partner/static/properties

Paginated full property rows — save entire object per hotel, not id only

GET/api/v1/partner/static/properties/{propertyId}

Single property detail (hotelId)

GET/api/v1/partner/static/rooms/{propertyId}

Static room metadata

GET/api/v1/partner/static/delta

Changed hotel IDs from watermark

POST/api/v1/partner/availability

Search or browse available room offers (hotel-level request)

POST/api/v1/partner/precheck

Validate chosen offer and lock booking context

POST/api/v1/partner/bookings

Create booking from precheck result

POST/api/v1/partner/bookings-list

Read multiple booking statuses

POST/api/v1/partner/bookings/{bookingGuid}/cancel

Cancel booking based on provider policy

GET/api/v1/partner/balance

Credit / 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 by dailyRates).
  • rateId appears in response only; you pass it to /precheck.
  • rateId is 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)

  1. Run full sync via /static/properties until hasMore=false; upsert every object in properties[] with all fields above.
  2. For each saved hotelId, load and persist full room metadata via /static/rooms/{propertyId}.
  3. Store nextUpdatedDateTs watermark.
  4. Schedule delta sync: call /static/delta?updatedDateTs=<watermark>.
  5. Refresh each changed hotel via /static/properties/{propertyId} (one HTTP call per hotel id).
  6. Update local watermark with returned nextUpdatedDateTs.

Rules and Limits

  • pageSize max 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 updatedDateTs invalid format, API returns INVALID_UPDATED_DATE_TS.
  • When hasMore=true on delta, continue immediately using returned nextUpdatedDateTs.

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 /balance to show “remaining credit” in partner admin UI.
  • Before bookPOST /bookings compares precheck.finalPrice + server buffer %; returns INSUFFICIENT_BALANCE (402) if not enough.

Alert levels

alertLevelMeaning (plain)Typical action
OkEnough credit for normal operationsNo action
WarningAvailable < 20% of credit limitShow banner; consider top-up
CriticalAvailable < 5% of limit, or zero without limitUrgent top-up
BlockedNot 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ường VND.
  • alertLevelOk | Warning | Critical | Blocked (enum string in JSON).
  • message — human-readable hint; may be null when Ok.

Booking rule: server requires availableBalance ≥ finalPrice × (1 + buffer%) on POST /bookings (buffer configured by rovitravel, e.g. 5%).

Recommended usage

  1. Show GET /balance on partner finance / admin page (refresh every 2–5 minutes).
  2. Surface alertLevel and message as UI banners when not Ok.
  3. On INSUFFICIENT_BALANCE from POST /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

CodeHTTPDescription
INVALID_PROPERTY400hotelId/propertyId missing or invalid.
PRICE_CHANGED409Price changed; rerun availability and precheck.
NO_INVENTORY409No rooms left for selected offer.
RATE_NOT_FOUND409Selected rate token no longer valid.
RATE_EXPIRED410/502Cached snapshot missing/expired.
PRECHECK_INVALID400Missing/expired/invalid precheck.
PRECHECK_SEARCH_ID_MISMATCH400searchId must match precheck and be string.
BOOKING_NOT_FOUND404Booking guid not found.
NONREFUND403Non-refundable cancellation blocked.
INSUFFICIENT_BALANCE402Not enough credit for booking (after buffer). Top up account.
BALANCE_SERVICE_NOT_CONFIGURED503Balance service endpoint missing on gateway.
INVALID_UPDATED_DATE_TS400Delta watermark format invalid.
DELTA_WINDOW_EXCEEDED400Delta lookback > 7 days.