ĐẶC TẢ KỸ THUẬT API: SEARCH & FILTER (v2.6)
Dành cho: Frontend Engineering Team (Web, Mobile App)
Dịch vụ: AI Search Engine API — LOTTE Mart Vietnam
Phiên bản:2.6.0| Cập nhật: 11/10/2026 | Giao thức: JSON over HTTP/2
1. THÔNG TIN ENDPOINT & GATEWAY
- HTTP Method:
POST - Content-Type:
application/json; charset=utf-8 - Accept:
application/json
1.1. Cấu trúc URL Endpoint (Đồng bộ tiền tố /api)
Hệ thống cung cấp 2 định dạng đường dẫn qua API Gateway:
-
Endpoint chuẩn hóa (Khuyến nghị sử dụng):
POST /api/v2/{lang}/{storeId}/products/search{lang}: Mã ngôn ngữ (vi|en|ko){storeId}: Mã siêu thị / chi nhánh (vd:nsg— LOTTE Mart Nam Sài Gòn)
-
Endpoint Alias tương thích:
POST /api/v2/p/mart/es/{storeId}/{lang}/products/search
(Gateway tự động chuẩn hóa và chuyển tiếp tương đương)
1.2. Danh sách Gateway Base URL theo môi trường
| Môi trường | Base Gateway URL | URL Endpoint hoàn chỉnh (Ví dụ Store NSG / Tiếng Việt) |
|---|---|---|
| DEV | https://dev-gateway.martonline.lotte.vn |
https://dev-gateway.martonline.lotte.vn/api/v2/vi/nsg/products/search |
| STAGING | https://staging-gateway.martonline.lotte.vn |
https://staging-gateway.martonline.lotte.vn/api/v2/vi/nsg/products/search |
| PRODUCTION | https://gateway.martonline.lotte.vn |
https://gateway.martonline.lotte.vn/api/v2/vi/nsg/products/search |
(Trong mạng nội bộ backend k8s / direct service: POST /api/v1/search)
2. REQUEST SPECIFICATION (THAM SỐ GỬI LÊN)
2.1. Cấu trúc JSON Request
| Trường root | Kiểu dữ liệu | Bắt buộc | Mặc định | Mô tả & Ràng buộc |
|---|---|---|---|---|
query |
string |
Có | — | Từ khóa tìm kiếm. Tối thiểu 1 ký tự sau trim. Chuỗi rỗng trả về HTTP 400. |
paginationMode |
string |
Không | "cursor" |
Chế độ phân trang: "cursor" (cho infinite scroll) hoặc "page" (cho click số trang). |
cursor |
string |
Không | null |
Token phân trang Base64 lấy từ nextCursor của response trước đó (dùng cho mode "cursor"). |
page |
number |
Không | 1 |
Số thứ tự trang (khi dùng paginationMode: "page", bắt đầu từ 1). |
pageSize |
number |
Không | 20 |
Số lượng sản phẩm trả về mỗi trang. Tối đa 100. |
sort |
string |
Không | "relevance" |
Tiêu chí sắp xếp: "relevance" (liên quan nhất), "price_asc" (giá tăng dần), "price_desc" (giá giảm dần), "newest" (mới nhất). |
filters |
object |
Không | {} |
Đối tượng chứa toàn bộ bộ lọc sản phẩm (Xem mục 2.2). Lưu ý: Không dùng tên cũ facetFilters. |
fields |
string[] |
Không | All | Danh sách các trường cần lấy để tối ưu băng thông (Xem mục 2.3). |
2.2. Chi tiết các cách Filter (filters)
Tất cả các bộ lọc bắt buộc nằm trong đối tượng filters. Giữa các bộ lọc khác nhau áp dụng toán tử AND.
Trường trong filters |
Kiểu dữ liệu | Toán tử | Cú pháp gửi lên | Mô tả hành vi & Quy tắc |
|---|---|---|---|---|
viTags |
string[] |
OR | ["Sữa Tươi", "Ít Đường"] |
Lọc theo tag tiếng Việt. Tối đa 10 items. Sản phẩm khớp nếu có ít nhất 1 tag trong mảng. |
enTags |
string[] |
OR | ["Fresh Milk"] |
Lọc theo tag tiếng Anh. Tối đa 10 items. |
krTags |
string[] |
OR | ["우유"] |
Lọc theo tag tiếng Hàn. Tối đa 10 items. |
tags |
string[] |
OR | ["Organic"] |
Tag alias chung đa ngữ. Tối đa 10 items. |
brandId |
string |
EQUAL | "Vinamilk" |
Lọc theo mã thương hiệu (Khớp chính xác). |
categoryId |
string |
PREFIX | "110" |
Lọc theo mã ngành hàng. Khớp tiền tố phân cấp (vd: "110" lấy cả con "11001", "11002"). |
priceMin |
number |
>= |
50000 |
Giá tối thiểu (VND). Bỏ qua nếu <= 0. |
priceMax |
number |
<= |
200000 |
Giá tối đa (VND). Phải >= priceMin. |
inStockOnly |
boolean |
EQUAL | true |
Chỉ trả về sản phẩm còn tồn kho (inStock: true). |
delivery |
string[] |
OR | ["express"] |
Phương thức giao: "express" (siêu tốc), "standard" (tiêu chuẩn). |
benefitFlags |
string[] |
OR | ["special_price"] |
Ưu đãi: "special_price" (giá sốc), "gift" (quà tặng kèm). |
ratingMin |
number |
>= |
4.0 |
Đánh giá tối thiểu từ 1.0 đến 5.0 (vd: 4.0 tương ứng từ 4 sao trở lên). |
attrs |
object |
OR (trong key) | {"brand_origin": ["Vietnam"]} |
Thuộc tính động chuẩn hóa (EAV). Hỗ trợ 8 keys: brand_origin, pack_type, volume, weight, flavor, target_user, usage_type, storage_condition. |
2.3. Chọn trường dữ liệu tối ưu (fields)
Frontend kiểm soát chính xác payload trả về bằng mảng fields:
- Khối cấp 1:
"products","facets","suggestedFilters". - Trường trong thẻ sản phẩm:
products.<field>(vd:products.id,products.name,products.price,products.imageUrl,products.inStock...). - Quy tắc tối ưu băng thông:
- Trang 1: Gửi
fields: ["products", "facets"]để lấy cả sản phẩm và cây bộ lọc. - Trang 2 trở đi (Infinite scroll): Gửi
fields: ["products"](bỏ qua tính toán facets, giảm 70% kích thước JSON).
- Trang 1: Gửi
3. RESPONSE SPECIFICATION (GIẢI THÍCH CHI TIẾT CÁC FIELD)
3.1. Các trường Siêu dữ liệu (Metadata)
Các trường này luôn luôn xuất hiện trong mọi response, không bị ảnh hưởng bởi tham số fields:
| Trường Response | Kiểu dữ liệu | Ý nghĩa & Mục đích sử dụng |
|---|---|---|
queryId |
string |
UUID phiên tìm kiếm. Dùng để gửi kèm event theo dõi log / click-through tracking. |
totalHits |
number |
Tổng số lượng sản phẩm thỏa mãn toàn bộ từ khóa và điều kiện lọc. Dùng hiển thị "Tìm thấy X sản phẩm". |
tookMs |
number |
Thời gian OpenSearch thực thi truy vấn (tính bằng milliseconds). |
paginationMode |
string |
Chế độ phân trang đang chạy ("cursor" hoặc "page"). |
page |
number |
Số trang hiện tại (chỉ có khi paginationMode: "page"). |
pageSize |
number |
Số sản phẩm được cấu hình trả về trên mỗi trang. |
hasMore |
boolean |
true nếu vẫn còn sản phẩm ở các trang tiếp theo; false nếu đã đến trang cuối cùng. Dùng để ngắt trigger infinite scroll. |
nextCursor |
string |
Chuỗi token Opaque Base64 đại diện cho vị trí tiếp theo. Frontend giữ nguyên giá trị này và truyền vào cursor ở request tiếp theo. |
resolvedMode |
string |
Mode thuật toán thực thi nội bộ ("text", "hybrid"...). |
corrected |
boolean |
true nếu từ khóa người dùng bị sai chính tả và hệ thống đã tự động sửa; false nếu từ khóa gốc được giữ nguyên. |
normalizedQuery |
string |
Chuỗi từ khóa sau khi đã chuẩn hóa và tự động sửa lỗi chính tả. Dùng hiển thị: "Kết quả cho từ khóa: ... " |
didYouMean |
string |
Từ khóa gợi ý nếu hệ thống phát hiện từ khóa khả dĩ khác. Nếu có, dùng hiển thị: "Có phải bạn muốn tìm: ... " |
3.2. Danh sách sản phẩm (products[])
Mỗi phần tử trong mảng products đại diện cho một thẻ sản phẩm hoàn chỉnh:
| Trường | Kiểu dữ liệu | Ý nghĩa |
|---|---|---|
id |
string |
Mã SKU định danh duy nhất của sản phẩm. |
name |
string |
Tên sản phẩm đầy đủ. |
price |
number |
Giá bán thực tế hiện tại (VND). |
originalPrice |
number |
Giá niêm yết ban đầu trước khi giảm giá (VND). |
discountRate |
number |
Phần trăm giảm giá (vd: 15 tức là giảm 15%). |
imageUrl |
string |
Đường dẫn ảnh đại diện sản phẩm (CDN). |
brandId |
string |
Mã định danh thương hiệu. |
brandName |
string |
Tên hiển thị của thương hiệu. |
categoryId |
string |
Mã ngành hàng của sản phẩm. |
categoryName |
string |
Tên ngành hàng phân cấp. |
inStock |
boolean |
Trạng thái tồn kho: true = còn hàng, false = hết hàng. |
stockQty |
number |
Số lượng tồn kho thực tế khả dụng. |
rating |
number |
Điểm đánh giá trung bình của sản phẩm (thang điểm 1.0 đến 5.0). |
reviewCount |
number |
Tổng số lượt khách hàng đánh giá sản phẩm. |
deliveryMethod |
string[] |
Các hình thức giao hàng hỗ trợ (vd: ["express", "standard"]). |
isNew |
boolean |
true nếu là sản phẩm mới về. Dùng gắn badge "MỚI". |
isBest |
boolean |
true nếu là sản phẩm bán chạy. Dùng gắn badge "BEST SELLER". |
isPromotion |
boolean |
true nếu sản phẩm đang áp dụng chương trình khuyến mãi. |
tags |
string[] |
Danh sách các tag nổi bật của sản phẩm. |
3.3. Dữ liệu bộ lọc (facets)
Đối tượng facets chứa số lượng đếm (aggregation count) của các bộ lọc để render sidebar và chip lọc:
| Nhóm Facet | Cấu trúc dữ liệu | Chi tiết & Cách xử lý ở Frontend |
|---|---|---|
viTags, enTags, krTags, tags |
FacetBucket[] |
Danh sách các tag và số lượng SP khớp. Cơ chế Self-exclusion: Tag đang được chọn vẫn hiển thị các tag anh em kèm số lượng nếu chọn thêm. |
brands |
FacetBucket[] |
Danh sách thương hiệu khớp. Gồm key (mã gửi lên filters.brandId), count, và label. |
categories |
FacetBucket[] |
Danh sách ngành hàng khớp. Gồm key (mã gửi lên filters.categoryId), count, và label. |
priceRanges |
FacetBucket[] |
Các phân khúc giá được tính toán sẵn. |
inStock |
FacetBucket[] |
Số lượng sản phẩm còn hàng (key: "true"). |
delivery |
FacetBucket[] |
Số lượng sản phẩm theo từng hình thức giao (key: "express", "standard"). |
benefitFlags |
FacetBucket[] |
Số lượng sản phẩm có ưu đãi (key: "special_price", "gift"). |
rating |
FacetBucket[] |
Số lượng sản phẩm theo các mốc sao. |
attrs |
Record<string, FacetBucket[]> |
LƯU Ý: Đây là Object Map, KHÔNG PHẢI MẢNG. Duyệt bằng Object.entries(facets.attrs). Mỗi key là tên thuộc tính (vd: "brand_origin"), value là mảng FacetBucket[]. |
Mỗi phần tử FacetBucket có cấu trúc:
{
"key": "Vinamilk",
"count": 45,
"label": "Vinamilk"
}
4. VÍ DỤ REQUEST VÀ RESPONSE THỰC TẾ
Request:
POST /api/v2/vi/nsg/products/search
Content-Type: application/json
{
"query": "sữa tươi",
"paginationMode": "cursor",
"pageSize": 20,
"sort": "relevance",
"filters": {
"viTags": ["Sữa Tươi", "Ít Đường"],
"brandId": "Vinamilk",
"inStockOnly": true
},
"fields": ["products", "facets"]
}
Response:
HTTP/2 200 OK
Content-Type: application/json; charset=utf-8
{
"queryId": "e3b0c442-98fc-1c14-9afe-432100000000",
"totalHits": 45,
"tookMs": 18,
"paginationMode": "cursor",
"pageSize": 20,
"hasMore": true,
"nextCursor": "ZXlKaGJHY2lPaUpTVXpVeE1pSXNJbXR...",
"resolvedMode": "hybrid",
"corrected": false,
"normalizedQuery": "sữa tươi",
"products": [
{
"id": "8934673123456",
"name": "Sữa Tươi Tiệt Trùng Vinamilk 100% Ít Đường 1L",
"price": 36500,
"originalPrice": 41000,
"discountRate": 11,
"imageUrl": "https://img.martonline.lotte.vn/p/8934673123456.jpg",
"brandId": "Vinamilk",
"brandName": "Vinamilk",
"categoryId": "11001",
"categoryName": "Sữa Tươi Tiệt Trùng",
"inStock": true,
"rating": 4.8,
"reviewCount": 124,
"deliveryMethod": ["express", "standard"],
"isPromotion": true,
"tags": ["Sữa Tươi", "Ít Đường"]
}
],
"facets": {
"viTags": [
{ "key": "Sữa Tươi", "count": 45, "label": "Sữa Tươi" },
{ "key": "Ít Đường", "count": 18, "label": "Ít Đường" }
],
"brands": [
{ "key": "Vinamilk", "count": 45, "label": "Vinamilk" }
],
"attrs": {
"brand_origin": [
{ "key": "Vietnam", "count": 45, "label": "Việt Nam" }
]
}
}
}
5. MÃ LỖI HTTP & NGUYÊN NHÂN TỪ CHỐI (HTTP STATUS CODES)
| Mã HTTP | Tình huống | Thông điệp lỗi mẫu | Nguyên nhân & Cách khắc phục |
|---|---|---|---|
200 OK |
Thành công | Dữ liệu theo schema SearchResponse. |
Xử lý bình thường. |
400 Bad Request |
Từ khóa rỗng | {"error": "query must not be empty"} |
Giá trị query bị rỗng sau khi trim. Client phải kiểm tra query.trim() trước khi gửi. |
400 Bad Request |
Sai cấu trúc | {"error": "unknown field \"facetFilters\""} |
Gửi tham số ngoài schema. Bắt buộc đổi tên thành filters. Không gửi trường dư thừa. |
400 Bad Request |
Quá giới hạn tag | {"error": "filters.viTags accepts at most 10 items, got 11"} |
Mảng tag vượt quá giới hạn 10 phần tử. Khống chế tối đa 10 tag được kích hoạt trên UI. |
400 Bad Request |
Sai cursor | {"error": "invalid cursor token"} |
Token cursor bị lỗi hoặc sửa đổi. Gửi đúng chuỗi nguyên bản từ nextCursor. |
500 Internal Error |
Lỗi máy chủ | {"error": "Internal search error"} |
Lỗi hạ tầng backend. Hiển thị thông báo thân thiện và nút thử lại. |