ĐẶ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:

  1. 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)
  2. 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).

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:

JSON Payload
{
  "key": "Vinamilk",
  "count": 45,
  "label": "Vinamilk"
}

4. VÍ DỤ REQUEST VÀ RESPONSE THỰC TẾ

Request:

JSON Payload
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:

JSON Payload
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.