Harthliving OTA · bản chữ cho AI · dựng từ vault (nguồn sửa lần cuối 29/09/2026 09:28) · mục lục: https://harthliving-ota.pages.dev/ai/index.txt URL (HTML): https://harthliving-ota.pages.dev/library/tech/agoda-ycs-automation/ [Tổng quan](https://harthliving-ota.pages.dev/) › [Dữ liệu & báo cáo](https://harthliving-ota.pages.dev/library/) › [Tài liệu kỹ thuật](https://harthliving-ota.pages.dev/library/tech/) # Agoda Partner Portal (YCS) — cẩm nang điều khiển ## Agoda Partner Portal (YCS) — cẩm nang điều khiển Nguồn: `platforms/agoda-ycs-automation.md` ### 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`](https://harthliving-ota.pages.dev/guide/chung/ota-messaging-rules/) §4. Luật set up listing ở [`agoda-listing-playbook.md`](https://harthliving-ota.pages.dev/guide/agoda/agoda-listing-playbook/). Kho tin nguồn ở [`vault/16-ota-messages/agoda/`]. ⚠️ **Mặc định Agoda là CHỈ ĐỌC** — [`vault/10-rules/ota-readonly-rule.md`](https://harthliving-ota.pages.dev/library/tech/ota-readonly-rule/). 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/` | | Đổi property | **Đổi thẳng `` 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`](https://harthliving-ota.pages.dev/properties/)): `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 - ` | đị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 ` · `--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 ` · `--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 ` + `--read` · `--download ` (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/`. 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//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=x` 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 `` 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/` 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`](https://harthliving-ota.pages.dev/status/agoda/alacarte305/#doc-agoda), sổ việc `L-101`. Lần hai: `alacarte502` 17/09/2026 — [`../properties/alacarte502/agoda.md`](https://harthliving-ota.pages.dev/status/agoda/alacarte502/#doc-agoda): 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`](https://harthliving-ota.pages.dev/guide/agoda/agoda-homes-content-score-guideline-2025/) (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/` | 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/` | `checkInCheckoutTime` · `checkInDetails` · `directions` · `parking` · `houseRules` | | Giờ nhận/trả | `POST …/ArrivalGuide/checkincheckouttime/` 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/` 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/` `{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/` — 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//` `{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/` 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 `'` trong parkingInstruction | | **Occupancy phòng** | `GET /en-us/api/setting/ChildRate/getroombypropertyId/` · `POST …/ChildRate/setuproomoccupancy/` `{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/` · `POST …/UpdateBookingSettings/` 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/` `{thingsNearby}` | | | **Room spec (diện tích · số WC)** | `POST /en-us/api/setting/RoomSettings/RoomSpecifications//` 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 ":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/` `{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/` 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 ":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//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/` — API vẫn là `GET/PUT /mldc/…/Facilities/` (440 dòng, PUT `{facilities:[{facilityId,isSelected}]}`) | | 522401 | Add **room amenities** | Rooms (kipp) → `POST /en-us//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/` — 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//kipp/api/roomtype/GetRoomType?roomTypeId=` → `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