OTA PortalHarth Living
Dữ liệu đọc tới 24/09/2026 (số liệu Booking) · PriceLabs 27/09/2026 · nguồn sửa lần cuối 29/09/2026 09:28
Tổng quan › Dữ liệu & báo cáo › Tài liệu kỹ thuật

Agoda Partner Portal (YCS) — cẩm nang điều khiển

Tổng quanBáo cáo & auditKho dữ liệuTài liệu kỹ thuậtVề dữ liệu

Agoda Partner Portal (YCS) — cẩm nang điều khiển

Nguồn: platforms/agoda-ycs-automation.md
Mục lục

Agoda Partner Portal (YCS) — cẩm nang điều khiển

Đúc từ phiên 05/09/2026: tạo 18 mẫu tin + bật Message scheduler cho 6 listing. Đây là tài liệu thao tác (điều hướng, DOM, bẫy tự động hoá). Luật nội dung tin nhắn ở vault/10-rules/ota-messaging-rules.md §4. Luật set up listing ở agoda-listing-playbook.md. Kho tin nguồn ở vault/16-ota-messages/agoda/.

⚠️ Mặc định Agoda là CHỈ ĐỌC — vault/10-rules/ota-readonly-rule.md. Ba script ghi bên dưới là ngoại lệ có chủ đích, chỉ chạy khi user ra lệnh trực tiếp cho đúng việc đó.


1. Điều hướng — URL là đường nhanh nhất

Cần tới Cách
Hộp thư khách của một property https://portal.agoda.com/mldc/en-us/app/hermes/inbox/ycs/<AGODA_ID>
Đổi property Đổi thẳng <AGODA_ID> trên URL — không cần bấm menu chọn căn ở thanh trên
Message scheduler Trong hộp thư → Automation tools → Scheduled messages
Auto reply Automation tools → Auto reply (khác Scheduled messages, chưa khảo sát)
Template replies Panel cố định bên phải hộp thư, có nút Create new

Breadcrumb khi vào scheduler: Guest messages > Message preference, hai tab Auto reply | Message scheduler.

Sáu Agoda ID (đối chiếu ../listings.md): 84463574 181 Tô Hiến Thành · 84463562 15 Nước Mặn 5 · 84463560 41 Lê Hy Cát · 63382515 A La Carte 305 · 84510952 A La Carte 404 · 84463577 A La Carte 502. 25anthuong11 không có listing Agoda.


2. Template replies — mẫu tin

🔴 Mẫu tin thuộc về TỪNG Hotel ID. Không dùng chung giữa các property. Cùng một nội dung vẫn phải tạo lại ở mỗi listing → 6 listing × 3 mốc = 18 mẫu. Tên trùng nhau giữa các căn là cố ý.

Giới hạn: ô Name 100 ký tự · ô Message 2.500 ký tự. DOM không đặt maxlength, nên phải tự đếm.

Quy ước đặt tên (user chốt 05/09/2026)

Mẫu Tên Vì sao
Xác nhận đặt phòng 01 Booking confirmed nội dung giống hệt mọi căn → không gắn tên căn
Nhận phòng 03 Check-in - <tên căn> địa chỉ, mã khoá, Wi-Fi khác nhau → riêng từng căn
Trả phòng 05 Check-out - villa hoặc 05 Check-out - A La Carte chỉ hai bản, chia theo giờ trả phòng và cách khoá cửa

Số 01/03/05 khớp tên file trong vault/16-ota-messages/agoda/, tra ngược được. Tên viết không dấu.

DOM của form mẫu tin

Thứ Selector Ghi chú
Ô tiêu đề input[name="Name"] hoặc input[placeholder*="title of your template"] 🔴 Có property không có attribute name — phải bắt kèm placeholder
Ô nội dung textarea[name="Message"] ổn định hơn
Nút khi TẠO Save / Discard
Nút khi SỬA Update / Cancel changes 🔴 tên khác hẳn — dùng nhầm là không tìm thấy nút
Update bị disabled tới khi React ghi nhận thay đổi phải poll vài nhịp, đừng bấm một lần rồi bỏ

3. Message scheduler — ba mốc gửi

Ba hàng, mỗi hàng một công tắc + accordion riêng. Bấm Save của chính hàng đó mới lưu.

Mốc Ô Select a time Lựa chọn
Upon booking confirmation không có gửi ngay khi đặt phòng được xác nhận
At check-in "Select a time before guest check-in" On the day of check-in · 1 day before · 2 days before · 3 days before
At check-out "Select a time after guest check-out" On the day of check-out · 1 day after · 2 days after · 3 days after

🔴 Hai mốc lệch NGƯỢC CHIỀU. Check-in đếm lùi trước ngày đến, check-out đếm tiến sau ngày đi → không có cách nào gửi tin trước ngày trả phòng trên Agoda. Bản Booking gửi tin trả phòng tối hôm trước; bê nguyên mốc đó sang Agoda là chọn nhầm.

Cấu hình đang dùng: booking = ngay khi xác nhận · check-in = 1 day before check-in · check-out = On the day of check-out.

Mỗi mốc chỉ gắn được MỘT mẫu tin → phải chọn: At check-out dùng cho tin dặn trả phòng hoặc tin cảm ơn sau kỳ nghỉ (1 day after check-out), không thể cả hai.


4. Bẫy tự động hoá qua CDP — đã trả giá

Nối Chrome đang đăng nhập qua CDP: xem scripts/ota-read-booking.mjs để lấy mẫu kết nối (đọc DevToolsActivePort, nối WebSocket, không gọi Page.enable — treo phiên trên portal OTA).

# Bẫy Cách đúng
1 Nút là SPAN/DIV chứ không phải BUTTON → .click() trong Runtime.evaluate vô hiệu Dùng Input.dispatchMouseEvent theo toạ độ getBoundingClientRect()
2 Thiếu mouseMoved trước mousePressed → React không nhận click Luôn bắn mouseMoved → mousePressed → mouseReleased
3 input#toggle là checkbox ẩn, kích thước 0 → bấm không ăn Bấm phần tử [role=switch] hiển thị; lấy cả 3 theo thứ tự DOM: 0 booking · 1 check-in · 2 check-out
4 Gán el.value = x thẳng → React không thấy, Save gửi chuỗi rỗng Native setter: Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set.call(el,v) rồi dispatchEvent(new Event('input',{bubbles:true}))
5 Đếm nút Edit theo chỉ số toàn trang → bấm nhầm Trang có 6 nút tên Edit: 3 của scheduler + 3 của panel Template replies. Tệ hơn: hàng đang bung ra thì mất nút Edit của chính nó, nên số nút tụt 3→2 và mọi chỉ số lệch. Phải giới hạn tìm kiếm trong panel scheduler trước khi đếm
6 Nút dropdown ở chế độ sửa hiện giá trị đang chọn, không phải chữ Select … Regex phải nhận cả hai: /^(Select template\|0[135] )/i
7 🔴 --set-method và --set-instructions dùng CHUNG endpoint ArrivalGuide/instructions, mỗi cái ghi null/0 vào trường của cái kia (dính 23/09/2026 ở 15nuocman5) Chạy --set-method TRƯỚC, rồi --set-instructions (nó đọc method hiện tại và giữ). Kiểm giữa chừng bằng --dry: body phải có "checkInMethod":5
8 🔴 GET của Agoda bị CACHE. Ghi mô tả xong đọc lại vẫn ra bản cũ → tưởng ghi hỏng, suýt ghi lại lần hai Đọc xác minh bằng fetch(url+'?_='+Date.now(),{cache:'no-store'}). Đừng kết luận "ghi không ăn" từ một lần GET thường
9 --set-facilities nhận MẢNG PHẲNG [{facilityId,isSelected,name,from}] Bọc {"facilities":[…]} là plan.map is not a function
10 Điểm ảnh phòng ngủ / phòng tắm không đến từ nhóm Property Đến từ nhóm ROOM (categoryId 7 + roomTypeId). Khuôn 100/100 của 41lehycat: Property 15 ảnh · Room 16 ảnh với Bedroom 6–7 + Bathroom 4–5
7 Dải cảnh báo chống lừa đảo của Agoda phủ lên nút Edit của Template replies → bấm theo toạ độ rơi vào banner, form không mở (gặp ở 502, 21/09/2026; 305 và 404 cùng lúc đó lại không dính) Nút này là BUTTON thật nên .click() trong Runtime.evaluate ăn. Ưu tiên DOM .click(), chỉ rơi về chuột CDP khi DOM không mở được form. Kiểm bằng document.elementFromPoint(cx,cy): trả về DIV lạ ⇒ đang bị che
8 Nút Edit của hàng scheduler là DIV, không phải BUTTON/SPAN/A → mọi selector querySelectorAll('button,span,a') bỏ sót hoàn toàn, báo "không thấy Edit". Ngược với #7, DIV này .click() không ăn Quét bằng querySelectorAll('*') lọc innerText.trim()==='Edit', chọn phần tử cùng dòng với nhãn hàng (\|y − y_nhãn\| < 30) rồi bấm bằng chuột CDP. Phân biệt hai bộ bằng toạ độ x: scheduler ở panel trái x≈1481, Template replies ở panel phải x≈1785

Chưa thông: ghi vào một hàng scheduler đã cấu hình. Công tắc đã bật thì bấm lại không tắt được, và đường qua Edit vướng bẫy #5. Hàng chưa cấu hình thì chạy ngon (15/15 mốc ở 5 listing). → Muốn sửa hàng đã cấu hình: làm tay, hoặc viết lại phần tìm Edit giới hạn trong panel scheduler.

🟢 ĐỌC hàng đã cấu hình thì thông rồi (21/09/2026). Bấm Edit của hàng (theo bẫy #8) là khung Message preview bên phải hiện tên mẫu đang gắn + toàn văn thân tin. Đây là cách rẻ nhất để xác minh một mốc đang gắn đúng mẫu nào — không phải mở form ghi, không chạm Save.

⚠️ scripts/ota-agoda-scheduler.mjs --probe hiện báo "không thấy mục Scheduled messages": Agoda đã đổi nhãn tab thành Message scheduler, script vẫn tìm chữ cũ. Chưa sửa.


5. Sự thật đã xác minh về hiển thị và nội dung

🟢 Agoda GIỮ dấu xuống dòng trong mẫu tin. Đọc ngược textarea từ portal thấy đúng ký tự \n; panel Template replies hiện rõ từng khối tách dòng. Format emoji + dòng trắng hiện đúng.

⚠️ Khung Message preview bên phải nuốt ngắt dòng thị giác — chữ chạy liền một mạch. Đó là CSS của khung preview, không phải tin thật. Đừng kết luận format hỏng từ khung này.

🔴 Agoda che dữ liệu: số thẻ · số điện thoại · email · URL → thay bằng X. Kakao ID / Zalo dạng chữ có thể lọt lưới — nhưng lọt là dính luật chống off-platform, xem ota-messaging-rules.md §4. Agoda dán sẵn câu mời khách tố cáo về safety@agoda.com trong mọi cuộc trò chuyện.

🔴 KHÔNG có placeholder. Không chèn được tên khách, không chèn được ngày → mẫu tự động là chữ chết. Cách né đang dùng: bỏ hết tên và ngày, thay bằng mốc tương đối mà chính scheduler bảo đảm (tomorrow cho tin check-in gửi trước 1 ngày, today cho tin check-out).

🟢 Agoda tự dịch tin sang ngôn ngữ khách khi gửi → không cần làm bản tiếng Hàn. ⚠️ Nhưng máy dịch chạy qua cả địa chỉ tiếng Việt, tên Wi-Fi và tên thương hiệu — Harth Living có chữ Living là từ có nghĩa, dễ bị dịch thành nghĩa đen. Chưa test.

🟢 Scheduler đọc thân mẫu SỐNG, không phải bản chụp lúc gán (xác minh 21/09/2026 trên 305). Sửa nội dung một mẫu trong Template replies là tin tự động của mốc đang gắn mẫu đó đổi theo ngay — không phải gỡ ra gắn lại. Bằng chứng: sau khi ghi đè 01 Booking confirmed, mở hàng Upon booking confirmation thì khung Message preview hiện đúng câu vừa thêm. → Hệ quả: đổi câu chữ chỉ cần chạy ota-write-agoda-templates.mjs --update, không đụng scheduler.

⏱️ Cửa sổ 14 ngày: không liên hệ được khách sau 14 ngày kể từ ngày trả phòng.


6. Ba script

Script Việc Chạy
scripts/ota-write-agoda-templates.mjs tạo / đổi tên / ghi đè thân tin mẫu, đọc nội dung từ khối ```text trong vault/16-ota-messages/agoda/*.md --dry · --only <id> · --rename · --update (sửa mẫu đã có cho khớp vault; so trước khi ghi, mẫu nào đã khớp thì bỏ qua; ghi xong mở lại form đọc lại rồi mới báo xong)
scripts/ota-agoda-scheduler.mjs bật 3 mốc + gắn mẫu --probe (không lưu) · --only <id> · --force
scripts/ota-agoda-photos.mjs ảnh: đọc · tải gốc · upload · đặt ảnh chính · chuyển nhóm Property⇄Room · gán tag (thêm 16/09/2026, xem §8) --id <id> + --read · --download <dir> (chỉ đọc) · --upload plan.json · --set-main · --reassign … --to room\|property · --tag, mọi lệnh ghi có --dry

Cả hai cần quyền Bash. Agent không tự cấp được — đưa dòng này cho user dán vào /permissions:

Bash(node scripts/ota-agoda-scheduler.mjs:*)

🔴 Rule không khớp vòng for của shell. for id in …; do node …; done bị chặn dù rule đã có. Phải gọi từng lệnh node trần một, mỗi listing một lượt.

Nội dung tin không gõ trong script — luôn đọc từ vault, để vault và portal không trôi khỏi nhau.


7. Còn chưa biết

  • Auto reply hoạt động ra sao (khác Scheduled messages) — chưa mở.
  • ~~Mã missingPhotoGroup chưa giải nghĩa~~ → đã giải 26/09/2026, xem bảng nhóm ảnh ở §10.
  • Agoda bắn tin At check-out vào giờ nào trong ngày → ảnh hưởng câu mở đầu Today, before 11:00 AM.
  • Máy dịch xử lý địa chỉ tiếng Việt, tên Wi-Fi, tên thương hiệu ra sao.
  • Bộ lọc có bắt nhầm mã cửa 8 số ([đã che]) hay mật khẩu Wi-Fi toàn số thành số điện thoại không.
  • Hồ sơ YCS đã khai số điện thoại chỗ nghỉ chưa — quyết định câu "call the property number shown on your booking confirmation" có đúng không.

8. Photos 2.0 — ảnh property và ảnh phòng (thêm 16/09/2026)

Trang portal.agoda.com/en-us/app/setting/photos/<AGODA_ID>. Toàn bộ API đọc từ bundle PhotosPage.*.js + api.*.js (theo read-wizard-copy-from-bundle-not-clicks), không cần bấm nút nào:

Việc Gọi gì Ghi chú
Đọc toàn bộ ảnh GET /en-us/api/setting/<id>/Photos/GetAllPhotos mảng nhóm: categoryId 5 = Property (roomTypeId 0) · 7 = Room (roomTypeId thật). Mỗi ảnh: photoId · photoToken · pictureTypeId (6 = ảnh chính, 5 = property, 7 = room) · pictureCaptionId (0 = chưa tag) · order
Bảng tag GET …/Photos/GetPhotoCaptions 97 tag; hay dùng: 13 Exterior view · 19 Lobby · 23 Reception · 9 Entrance · 21 Swimming pool · 25 Restaurant · 5 Buffet · 22 Bar/lounge · 8 Coffee shop · 11 Fitness center · 27 Spa · 121 Massage · 125 Sauna · 154 Bedroom · 3 Bathroom · 139 Separate living room · 102 Kitchen · 1 Balcony/terrace
Upload POST …/Photos/UploadPhoto multipart CategoryId · RoomTypeId · CaptionId · Order · FormFile trả {photoId, photoToken, recStatus} — recStatus 1 = lên ngay, khác 1 = chờ duyệt. Gán CaptionId ngay lúc upload thì không dính "Missing tags"
Tag · đặt ảnh chính · chuyển nhóm POST …/Photos/UpdatePhotos {updatePhotoInfoList:[{photoId, photoToken, roomTypeId, imageType, captionId, order}]} cùng một endpoint: đổi captionId = tag · imageType 6, order 1 = ảnh chính (ảnh chính cũ phải hạ về 5) · đổi imageType 5↔7 + roomTypeId = chuyển Property⇄Room không cần tải lại (giữ nguyên gốc)
Ảnh gốc lấy uri từ API, thay s=<W>x<H> bằng width×height API trả giữ nguyên ce= (ảnh mới ce=3; ép ce=0 → CDN trả 1×1); không có s= chỉ được 2048px. Agoda ép cạnh dài ảnh upload về 2048px — bộ 6016px của 2024 là đường upload cũ

Bẫy đã gặp: - Ảnh chính (pictureTypeId 6) không chuyển nhóm / không xoá được khi đang là ảnh chính → đặt ảnh chính mới trước (--set-main), rồi mới --reassign. - Sau mỗi UpdatePhotos, server đánh số lại order của cả nhóm Room (1→24) theo thứ tự riêng của Agoda — order trên YCS không phải thứ tự mình đặt, đừng dùng nó làm khoá; dùng photoId. - Header chỉ có chuẩn + Request-Id/traceparent, không có CSRF → fetch() cùng origin trong tab là đủ; upload đi qua DOM.setFileInputFiles vào <input type=file> tạm do script tạo (không đụng input của app). - 🔴 Upload trả 409 "duplicate" khi file định upload trùng md5 với ảnh đang có trên listing (41LHC 17/09: 70 ảnh cũ chính là file vault) → re-encode (thumbnail 2048 + save lại) trước khi upload, rồi mới xoá bản cũ. - 🔴 Mã ảnh trên CDN = md5 của file upload, và bản ghi khoá theo md5: tải lại đúng file đã xoá thì Agoda trả cùng photoId cũ và ảnh sống lại trong danh sách (đã trả giá 16/09: agent tải lại restaurant-01 để dò lỗi, làm sống lại tấm user vừa xoá tay). Thấy ảnh biến mất → hỏi user trước, đừng tải lại để thử. - Sau upload, CDN trả ảnh 1×1 và API trả 0x0 trong ít nhất 30 phút — không phải lỗi, là xử lý bất đồng bộ. - Trang khách (agoda.com) trễ theo: ngay sau khi đổi ảnh (upload · reassign · tag hàng loạt), card tìm kiếm + gallery trang chi tiết rút xuống chỉ còn 1–2 ảnh (contentImages.hotelImages), trong khi listing khác vẫn 10. Đo 16/09: 2 ảnh lúc 16:45 → đủ 10 ảnh (trần của card) lúc 17:10, tức ~30 phút sau lần UpdatePhotos cuối. Đừng kết luận mất ảnh từ trang khách trong vòng 1 giờ sau khi sửa. Trang chi tiết còn tự chèn 4 ảnh "Nearby attraction" (providerId 999979) không phải của mình. - 🔴 GET …/Facilities/<id> bị cache trong tab: PUT xong đọc lại ngay bằng fetch() mặc định vẫn thấy giá trị cũ (16/09 tưởng lưu hỏng). Luôn fetch(url,{cache:'no-store'}) hoặc thêm ?_=Date.now() khi đọc lại sau khi ghi. - photoToken là handle của từng ảnh — không lưu vào vault (luật §9), script --read lấy lại được.

Tiêu chí ảnh lên trang khách (đo 16/09/2026 trên 305 + đối chứng 502, 41 LHC): - Gallery trang chi tiết (contentImages.hotelImages) = toàn bộ Property photos (305: 11/11) + chỉ 2 Room photos (305: 2/24 · 502: 2/24 — cùng mẫu) + 4 ảnh "Nearby attraction" Agoda tự chèn. 22 ảnh phòng còn lại không vào gallery — chỉ hiện trong card phòng khi ngày tìm còn phòng (chưa xem được vì hai ngày thử đều Sold out). - Card tìm kiếm = 10 ảnh đầu của gallery (trần 10, mọi listing đều 10). - Thứ tự: ảnh chính property (pictureTypeId 6) luôn đứng #1; từ #2 trở đi không theo order trên YCS mà theo "AI smart ordering" của Agoda (305: hồ bơi → DELI → spa → nhà hàng → buffet → bar → hồ bơi → gym → massage → hồ bơi bình minh — xen kẽ nhóm Facilities/Dining, đẩy gym/massage xuống cuối). Kéo-thả trên YCS chỉ đổi order, chưa thấy tác dụng lên trang khách. - 502 có 4 ảnh trên card không tồn tại trong YCS → là ảnh Agoda nhập từ Booking.com (đúng như chân trang YCS nói). 305 không có ảnh nhập kiểu này. - Muốn một tấm lên card: đặt vào Property photos + gắn tag; muốn nó đứng đầu: đặt làm Main photo; vị trí 2–10 không điều khiển được.

Lần đầu áp dụng: alacarte305 16/09/2026 — hồ sơ ../properties/alacarte305/agoda.md, sổ việc L-101. Lần hai: alacarte502 17/09/2026 — ../properties/alacarte502/agoda.md: 30 ảnh (17 Property + 13 Room) tag ngay lúc upload, 3 bản sao nội thất re-encode 2048px cho md5 khác bản Room, xoá 37 ảnh cũ bằng --delete.


9. Content score (Homes) · Arrival guide · Host profile (thêm 16/09/2026)

Script: scripts/ota-agoda-content.mjs — --score · --read (chỉ đọc) · --set-times · --set-method · --set-bio (ghi, có --dry). ⚠️ Mục này là rubric Homes; property ở chế độ khách sạn (xem §10) chấm 11 mục khác. Luật chấm điểm chính thức: agoda-homes-content-score-guideline-2025.pdf (Photos 30 · Arrival guide 30 · Amenities 15 · Host 15 · Description 10; điểm refresh ≤24 h).

Việc API Ghi chú
Điểm từng mục GET /mldc/en-us/api/reporting/AnalyticsCenter/ContentScoreDetails/<id> 18 mục, isImproveNeeded + improvements[] (gợi ý chung, không phải tiện ích đã tick)
Arrival guide đọc GET /en-us/api/setting/ArrivalGuide/<id> checkInCheckoutTime · checkInDetails · directions · parking · houseRules
Giờ nhận/trả POST …/ArrivalGuide/checkincheckouttime/<id> body = khối checkInCheckoutTime giờ dạng "2:00 PM"; null hiện là "Flexible" trên UI nhưng không được tính điểm
Cách nhận phòng POST …/ArrivalGuide/instructions/<id> body = khối checkInDetails checkInMethod 1 Reception · 2 Meet and greet · 3 Key self-collection · 4 Lockbox · 5 Door code · 6 Secret key · 7 Email · 8 Other
Host profile GET /mldc/en-us/api/iam/HostProfile/ViewModel · POST …/HostProfile/SaveHostProfile cấp tài khoản (chung 6 listing); bio 20–500 ký tự, score đòi ≥100

| Mô tả property | POST /en-us/api/setting/PropertyDetails/UpdatePropertyDescription/<id> {description} | client chặn URL (/(https?:\/\/[^\s]+)/); lưu là lên ngay, không qua duyệt (đọc lại GeneralInformation thấy ngay) | | Facilities | GET/PUT /mldc/en-us/api/setting/Facilities/<id> — PUT {facilities:[{facilityId,isSelected}]} chỉ gửi dòng thay đổi | 88 mục / 8 nhóm; isSelected null = chưa khai · false = đã khai không | | Giường | POST /en-us/api/setting/RoomSettings/BedConfigurations/<id>/<roomTypeId> {bedroomConfigurations:[{productBedTypeId,numberOfBed,bedroomGroupNumber,optionId:1,productBedTypeLocalName:null}],commonRoomConfigurations:[]} | bedType 1 Single · 3 Double · 4 Queen · 5 King · 8 Sofa; trả true | | House rules · parking · directions | POST …/ArrivalGuide/houserules|parking|directions/<id> body = đúng khối trong GET | airport transfer: isOffered=true bắt buộc có feeValue (0 = miễn phí); server HTML-encode dấu ' thành &#39; trong parkingInstruction |

| Occupancy phòng | GET /en-us/api/setting/ChildRate/getroombypropertyId/<id> · POST …/ChildRate/setuproomoccupancy/<id> {setAdultChild:true, rooms:[{roomTypeId, adultSettings:[{baseOccupancy, guestAllowed, maxAdult, maxChildren}], extraBedSettings:[{numberOfExtraBed, numberOfBabyCot}]}]} | 🔴 Toàn null = Agoda bán mặc định 2 người lớn dù Rooms khai max 4 (305 mất mọi khách 3–4 người tới 16/09). Sau POST, rate plan tự sinh occupancyRate {1..n} cùng giá; trang UI childrenrateocc lỗi với property Homes, chỉ API chạy; GET đọc lại vẫn null một lúc — tin response POST + GetRate.occupancyRate | | Mô tả trên trang khách | contentDetail.contentInformation.description.short (GraphQL agoda.com) | Agoda tự sinh "About us" từ dữ liệu tiện ích, không đăng mô tả partner (đo 16/09 trước và sau khi đổi mô tả). Mô tả partner chỉ còn ý nghĩa cho Content Score / app; sai tiện ích (vd Free parking) sẽ lộ ngay ở đoạn tự sinh |

| Booking settings | GET /en-us/api/supply-pricing-metadata/BookingSettings/GetViewModel/<id> · POST …/UpdateBookingSettings/<id> body = {bookTypeId:{isDirty,value,oldValue}, cancellationPolicyId:{…}, ratePlanSetting:{isDirty,value:[{ratePlanId,maxAdvPurchase,minAdvPurchase,minNightsStay,maxNightsStay}],oldValue:[…]}, dateBasedStayConditions:{…}} | -1 = No limit / không giới hạn; không bọc bookingSettingUpdateModel (400 "RatePlanSetting field is required") | | Local recommendations | GET/POST …/ArrivalGuide/thingsnearby/<id> {thingsNearby} | | | Room spec (diện tích · số WC) | POST /en-us/api/setting/RoomSettings/RoomSpecifications/<id>/<rt> body = {maxOccupancy:{isDirty,value,oldValue}, isChildrenAllowed:{…}, maxAllowedFreeChildren:{…}, noOfBathroom:{…}, sizeOfRoom:{…}, externalRoomTypeCode:{…}} (cùng mẫu dirty của BookingSettings; đọc từ bundle RoomSetupBasicDetailsPage + useSaveRoomSpecifications 17/09) | --set-room-spec "<rt>:size=50,bathrooms=1"; trả true; GET đọc lại thấy ngay | | Địa chỉ ngôn ngữ khác | trong PUT PropertyLocation: localizedStreetAddresses:[{productAddressId:0, languageId, streetName}] | ALL_LANGUAGE: 24 = Tiếng Việt; server trả về kèm productAddressId thật |

  • 🔴 Nhãn trên trang khách lấy từ taxonomy KHÁCH SẠN, cùng id với toggle Homes: Keyless access (272) hiện thành "Check-in [24-hour]"; Gym (57) hiện "Fitness center — Free" (không có cờ paid — FacilityProperties: []). Bật toggle Homes là chấp nhận nhãn khách sạn tương ứng. Đối chiếu id ở kipp …/usefulinformation → propertyFacilitiesApi/GetViewModel.

  • 🔴 Ô bio Host profile là React controlled — native setter + dispatch input KHÔNG ăn (ô về 0/500 ngay). Phải Input.insertText qua CDP sau khi focus() (gõ như người). Bảng SET_VAL ở §4 chỉ đúng với form Template replies.

| Đổi tên listing | POST …/PropertyDetails/UpdatePropertyName/<id> {propertyName, propertyLocalName} | lưu ngay, propertyLanguageName tự bằng tên mới; luật đặt tên Agoda cấm ngoặc/khuyến mãi — user tự chịu | | Check-in instructions | POST …/ArrivalGuide/instructions/<id> body = khối checkInDetails (giữ checkInMethod) | ≥25 ký tự; Directions không hiện cho khách, muốn khách đọc điều kiện thì viết vào instructions/tên |

  • 🔴 Airport transfer trên Homes chỉ có 3 ô Có/Phí/Phút, không có ô điều kiện → bật là Agoda tự viết "Free airport transfer" + FAQ "Yes" + đoạn AI cho MỌI booking. Điều kiện phải gài ở tên listing + check-in instructions + mẫu tin (305, 16/09).

Luật chấm cần nhớ (đọc lại PDF 17/09): Check-out Time: Specify the check-out from and check-out until time or indicate if it is flexible → chỉ khai checkOutUntil là 0/4 (305 vẫn 0/4 sau khi 91/100). 502 khai cả checkOutFrom = 6:00 AM + checkOutUntil = 12:00 PM (user chốt 17/09) — chờ refresh để xác nhận. Ảnh bedroom/bathroom ở nhóm Room vẫn được tính (305 lên 4/4 + 4/4 chỉ với tag ở Room).

Bẫy 17/09 (502): PUT Facilities trả 200 nhưng GET no-store ngay sau vẫn null cả 88 — PUT lại lần hai → 88/88 khớp. Chưa rõ do xử lý bất đồng bộ hay lần đầu không ăn; luôn đọc lại và so từng dòng với plan trước khi báo xong. Lặp lại ở 404 (23:10): lần 1 chỉ 59/88 ăn (29 dòng nhóm Guest favorites còn null), PUT lại → 88/88. Coi như quy tắc: PUT hai lần, đọc lại giữa hai lần. Cùng ngày: setuproomoccupancy đặt maxAllowedFreeChildren về 0 (305 16/09, 404 17/09) → muốn giữ 1 phải chạy --set-room-spec "<rt>:children_free=1" SAU occupancy. Cùng ngày: 502 đang Non-refundable (13308) chứ không phải 949 như 305 — đừng suy chính sách từ căn này sang căn khác, đọc GetViewModel trước khi hỏi user.

Mục "Add more photos of your property facilities" (2 điểm, 18/09): Agoda dò từng tiện ích đã tick Yes xem có ảnh gắn đúng caption tag hay chưa và liệt kê tên tiện ích thiếu trong improvements[] (41LHC: Bathtub · Dryer; 181: Dryer). Tag Bathroom (3) KHÔNG đếm cho Bathtub — phải là Bathtub (136); ảnh máy giặt tag Washing machine (144) không đếm cho Dryer — cần thêm ảnh tag Clothes dryer (137) dù máy sấy xếp ngay trên máy giặt trong cùng khung. Cách vá rẻ nhất: đổi tag bản Property của ảnh WC có bồn (nhóm Room vẫn giữ đủ Bathroom = số WC) và upload thêm một bản ảnh giặt là re-encode (md5 khác, tránh 409) tag 137. Bảng tag đầy đủ: GET …/Photos/GetPhotoCaptions (Private pool 138 · Sauna 125 · Swimming pool 21 · Garden 38 · Fitness center 11 · Hot tub 17 · Parking lot 166).

Bẫy đã trả giá 16/09: - 🔴 SaveHostProfile đòi số điện thoại đã xác minh OTP — handler Submit của form kiểm phoneVerified && phoneNumber trước khi gửi; gọi API thẳng khi chưa có SĐT → 500 "Failed to save host profile". Agent không xác minh OTP được → user làm tay (Host profile → Edit → Phone → Verify). - photoUrlString phải rỗng/null hoặc /avatar/<64hex>_<32hex>.jpg — gửi lại URL pix6.agoda.net cũ là 400 regex; form gốc gửi null khi không đổi ảnh. - userProfileType chỉ nhận 1 (Individual) / 2 (Company) / null.



10. Property ở chế độ YCS KHÁCH SẠN (không phải Homes) — thêm 19/09/2026

Nhận biết: GET /mldc/en-us/api/layout/<id>/PrivateLayout/Main → MenuViewModel.IsNonHotelAccommodation = false (Homes = true). Dấu hiệu trên UI: star rating có giá trị (5) · menu Rate plans · Room mapping · Cancellation policies · Children Rate and Occupancy · trang Facilities hiện 440 mục / 16 nhóm (Guest favorites 15 · Things to do 47 · Dining 60 · … · Languages 44 · Payment 63) thay vì 88 toggle / 8 nhóm · không có Host profile card. isNhaEnabled trong GeneralInformation và isNhaProperty trong ArrivalGuide vẫn true — không tin hai cờ đó. Lần đầu gặp: 25anthuong11 96450982, sinh ra từ List my property → Fast-track import (URL Booking) 18/09/2026 — wizard không có bước chọn Hotel/Homes; 5 căn tạo trước đó đều là Homes. Không có toggle/API đổi loại hình → xin support (L-120).

Rubric content score bản khách sạn = 11 mục (CMS id đọc từ bundle ContentScorePage.*.js, chữ lấy bằng POST /mldc/en-us/api/reporting/Cms/MultiGet body {languageCode:'en-us',origin:'VN',ids:[…]}):

CMS Mục Trang sửa
522393 · 522394 Add more photos of your property · high-resolution Photos (API Photos 2.0 như §8 — dùng chung cả hai chế độ)
522395 Add more photos of your property facilities Photos — tag khớp facility đã Yes (Parking lot 166 ↔ Car park · Kitchen 102 · Washing machine 144)
522396 · 522397 · 522398 photos of your rooms · high-res · photos of your room amenities Photos nhóm Room (Bedroom 154 · Bathroom 3 · Balcony 1 · Kitchen 102)
522399 · 522400 essential / additional property facilities and services /en-us/kipp/app/settings/propertysetting/propertyservices/<id> — API vẫn là GET/PUT /mldc/…/Facilities/<id> (440 dòng, PUT {facilities:[{facilityId,isSelected}]})
522401 Add room amenities Rooms (kipp) → POST /en-us/<id>/kipp/api/roomtype/UpdateRoomType
522402 Update your star rating chỉ qua chat Agoda (#chat) — import đã gán 5
522403 guest age policy /en-us/app/setting/childrenrateocc/<id> — 4 POST ChildRate/setup*

Nhóm ảnh missingPhotoGroup — giải 26/09/2026 từ bảng ks trong bundle es.*.js của trang Photos (không bấm thử; kiểm chứng bằng chính API: gắn tag xong, số nhóm tụt ngay trong GetAllPhotos):

Nhóm Caption id thuộc nhóm Nghĩa Mục content score dò nó
1 8 Coffee shop · 25 Restaurant · 165 Restaurant (private room) Nhà hàng 522395 (nếu tick tiện ích nhóm Dining)
2 5 Buffet · 104 Food and beverages Ăn uống 522395
3 21 Swimming pool · 122 Pool [outdoor] · 138 Private pool Hồ bơi 522395
4 27 Spa · 32 Beauty salon · 119 Hot spring bath · 121 Massage · 125 Sauna Spa 522395
5 11 Fitness center · 153 Gym/fitness Gym 522395
6 36 Kid's club · 37 Playground · 142 Pool [kids] · 151 Kids areas Trẻ em 522395
7 24 Recreational facilities · 40 · 41 · 101 · 103 · 105 · 112 · 113 · 114 · 118 · 124 · 126 · 131–135 · 147 · 149 "Things to do" 522395 — improvementKeyId 7 = đúng nhóm này
8 3 Bathroom Phòng tắm (Room) 522398 room amenities
9 110 Bed Giường (Room) 522398
10 29 View Tầm nhìn (Room) 522398

⇒ Mục 522395 bản khách sạn đòi ảnh theo nhóm tiện ích đã tick (tick Karaoke/Massage chair thuộc nhóm facility Things to do → cần ≥1 ảnh Property mang caption nhóm 7, vd 24 Recreational facilities). Mục 522398 đòi Room có đủ nhóm 8 · 9 · 10 — tag Bedroom (154) không thay được Bed (110). missingPhotoGroup liệt kê cả nhóm của tiện ích không tick (25AT11 thiếu [1..7] nhưng improvements[] chỉ nêu Things to do) — suy ra chỉ nhóm khớp tiện ích đã tick mới bị chấm; xác nhận khi điểm refresh. Lần đầu áp dụng: 25AT11 26/09 (97 → chờ refresh).

Bộ Homes (522415–522432: 18 mục — ảnh 6 · amenities 2 · mô tả · host 3 · arrival guide 6) không áp cho property hotel; nhưng Arrival guide/mô tả vẫn ghi được bằng API §9 và nên làm sẵn để đổi loại hình không phải làm lại.

API kipp (đọc từ cdn5.agoda.net/ycs/dist/22-*.js + 184-*.js): - Room: GET /en-us/<id>/kipp/api/roomtype/GetRoomType?roomTypeId=<rt> → Data.Room (Facilities = {Access,Accessibility,Bath,Clothing,Comfort,Dining,Entertainment,Layout,Safety,Others:[id]} · IsChildrenAllowed · NoOfBathroom · SizeOfRoom · ViewId · RoomBedConfigs). Bảng id→tên: …/roomtype/getRoomTypeMasterData (Facilities 10 nhóm · HotelViews · BedTypes). Ghi: POST …/roomtype/UpdateRoomType body = đúng object Room + hotelId, language:'en-us', RoomTypeId (builder c=function(e,t,n,l,…) trong bundle 22). Script: scripts/ota-agoda-room-amenities.mjs --read / --apply plan.json [--dry], tự đọc lại so từng nhóm. Hằng số: nonSmoking 15 · bathtub 37 · smokingAllowed 281. Server bỏ qua MaxAdultsOccupancy/MaxChildrenOccupancy (null), NoOfBedroom tự tính từ bed config. - Property facilities kipp: GET …/kipp/api/propertyFacilitiesApi/GetViewModel?facilityGroupIds= · POST …/UpsertHotelFacilities — không cần, PUT /mldc/…/Facilities ăn cả 440 (19/09: lần 1 khớp 440/440). - Age policy (mldc ChildRate, Content-Type: application/json-patch+json, body thường): setupagepolicy {minimumAgeAllowed:0, childRateEnabled:true, childAgeRange:[{ageFrom:0, ageTo:2, recStatus:1, needExtraBed:false}]} → trả childAgeRanges[].hotelChildAgeRangeId · setupuniversalrate {mode:1, ageRanges:[{ageRangeId, stayRate:{value:0, type:4}, breakFast:{}}]} (pricingType 1 Flat · 2 Discount adult · 3 % adult · 4 Free · 5 Not provided) · setupextrabed {isExtraBedEnabled:false, ageRequireExtraBed:null, ageRanges:[], isChannelManager:false} · setuproomoccupancy như §9 · setupfacilities (child facilities, tuỳ chọn). Đọc lại: childviewmodel (ageRange, hotelOccupancy.childRateModeId) + trang wizard hiện Active. Bed config vẫn ghi qua mldc BedConfigurations (chạy được ở hotel-mode).

Bẫy 19/09: - 🔴 Ảnh dọc 2:3 (2000×3000 hay 1365×2048) bị Agoda ép về cao 1536 → rộng 1023, dưới ngưỡng 1024×768 của mục high-resolution. Resize dọc về đúng 1030×1536 (cắt 9 px) trước khi upload thì Agoda giữ nguyên (181 qua với 1030×1536). - Ghi hai lần lặp lại: UpdateRoomType lần 1 chỉ ăn IsChildrenAllowed, amenities ăn lần 2; UpdatePropertyDescription lần 1 đọc lại 0 ký tự, lần 2 ăn. Luôn đọc lại no-store so plan. - ContentScoreDetails trả 204 cho listing mới (cả 404 hôm 17/09) — không phải lỗi, chờ ≤24 h; trang Analytics hiện "No content score data at the moment". - Tab CDP: chrome-grab mở/đổi tab nên script tự viết phải chọn tab theo URL (TAB=childrenrateocc), không lấy tab portal.agoda.com đầu tiên.

Liên quan: Agoda — cơ chế đã hiểu · Cẩm nang set up listing Agoda · issues.md L-15 · Luật tin nhắn OTA · Luật chỉ đọc OTA