# CheckWeb Public API

API chỉ đọc để website hoặc ứng dụng khác sử dụng dữ liệu đã được CheckWeb đồng bộ lên Supabase.

## Thông tin kết nối

- Base URL: `https://checkpolice-catalog.pages.dev/api/v1`
- Xác thực: không cần API key
- Phương thức: `GET`, `HEAD`, `OPTIONS`
- Định dạng: JSON
- Số bản ghi tối đa: 100 mục mỗi trang

Không sử dụng Supabase secret key hoặc các key Logo.dev, Serper và Firecrawl trong ứng dụng gọi API.

## Bắt đầu nhanh

```js
const API_BASE = "https://checkpolice-catalog.pages.dev/api/v1";

const response = await fetch(`${API_BASE}/brands?page=1&limit=20`);
if (!response.ok) throw new Error(`CheckWeb API: ${response.status}`);

const { data, meta } = await response.json();
console.log(data);
console.log(meta);
```

Response thành công có dạng:

```json
{
  "data": [],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 35
  }
}
```

## Endpoint

### Thông tin catalog

```http
GET /meta
```

Trả tổng số thương hiệu, sản phẩm và thời điểm đồng bộ gần nhất.

### Danh sách thương hiệu

```http
GET /brands?page=1&limit=20
GET /brands?q=nike
GET /brands?status=VERIFIED
GET /brands?affiliate=YES
GET /brands?network=Awin
GET /brands?hasTraffic=true
```

Tham số:

- `q`: tìm theo tên thương hiệu hoặc domain.
- `page`: trang, bắt đầu từ 1.
- `limit`: 1–100.
- `status`: trạng thái website, ví dụ `VERIFIED` hoặc `USER_PROVIDED`.
- `affiliate`: `YES`, `NO` hoặc `UNKNOWN`.
- `network`: tên affiliate network.
- `hasTraffic`: `true` hoặc `false`.

### Chi tiết thương hiệu

```http
GET /brands/{slug}
```

Ví dụ:

```js
const result = await fetch(`${API_BASE}/brands/nike`).then(r => r.json());
```

Chi tiết có thể gồm domain chính thức, affiliate, email liên hệ, traffic và sản phẩm.

Danh sách và chi tiết brand có trường `socialLinks`, tối đa một profile chính thức cho mỗi nền tảng:

```json
{
  "socialLinks": [
    { "platform": "INSTAGRAM", "url": "https://www.instagram.com/example" },
    { "platform": "YOUTUBE", "url": "https://www.youtube.com/@example" }
  ]
}
```

### Sản phẩm của thương hiệu

```http
GET /brands/{slug}/products
```

### Tra cứu bằng domain

```http
GET /domains/{domain}
```

Ví dụ:

```js
const result = await fetch(
  `${API_BASE}/domains/${encodeURIComponent("nike.com")}`
).then(r => r.json());
```

### Danh sách sản phẩm

```http
GET /products?page=1&limit=20
GET /products?q=shoe
GET /products?category=Shoes
GET /products?brand=nike
```

### Chi tiết sản phẩm

```http
GET /products/{product_key}
```

Kết quả chi tiết có thể gồm ảnh và biến thể sản phẩm.

## Ví dụ React

```jsx
import { useEffect, useState } from "react";

export function BrandList() {
  const [brands, setBrands] = useState([]);

  useEffect(() => {
    fetch("https://checkpolice-catalog.pages.dev/api/v1/brands?limit=20")
      .then((response) => {
        if (!response.ok) throw new Error("Không tải được dữ liệu CheckWeb");
        return response.json();
      })
      .then((result) => setBrands(result.data || []))
      .catch(console.error);
  }, []);

  return (
    <ul>
      {brands.map((brand) => (
        <li key={brand.brand_key}>
          {brand.original_name} — {brand.preferred_domain}
        </li>
      ))}
    </ul>
  );
}
```

## Ví dụ Next.js

```js
export async function getCheckWebBrands() {
  const response = await fetch(
    "https://checkpolice-catalog.pages.dev/api/v1/brands?limit=100",
    { next: { revalidate: 300 } }
  );

  if (!response.ok) throw new Error("CheckWeb API không phản hồi");
  return response.json();
}
```

## Xử lý phân trang

Không đặt `limit` lớn hơn 100. Nếu cần toàn bộ dữ liệu, tăng `page` cho tới khi đã nhận đủ `meta.total`.

```js
async function getAllBrands() {
  const all = [];
  let page = 1;

  while (true) {
    const response = await fetch(
      `${API_BASE}/brands?page=${page}&limit=100`
    );
    if (!response.ok) throw new Error(`HTTP ${response.status}`);

    const result = await response.json();
    all.push(...(result.data || []));

    if (all.length >= Number(result.meta?.total || 0)) return all;
    page += 1;
  }
}
```

## Trạng thái và lỗi

- `200`: thành công.
- `404`: không tìm thấy brand, domain hoặc sản phẩm.
- `405`: phương thức không được hỗ trợ.
- `429`: gọi quá nhanh; chờ rồi thử lại.
- `502` hoặc `503`: nguồn dữ liệu tạm thời chưa sẵn sàng.

Luôn kiểm tra `response.ok` trước khi đọc `data`.

## Dữ liệu được công khai

API có thể trả:

- Thương hiệu và domain chính thức.
- Trạng thái xác minh.
- Affiliate network, apply URL và link affiliate.
- Email liên hệ.
- Traffic.
- Sản phẩm, biến thể và ảnh sản phẩm.
- Profile Instagram, Facebook, TikTok và YouTube được tham chiếu từ homepage chính thức.

Banner homepage, raw scrape, bằng chứng debug và API key hiện không được đưa lên Public API.

## Đồng bộ dữ liệu

SQLite trong CheckWeb là nguồn chính. Sau khi app quét xong, dữ liệu được xếp hàng đồng bộ lên Supabase. Website khác đọc dữ liệu đã đồng bộ từ API Cloudflare và không cần kết nối tới app local.

Xem tài liệu tương tác: https://checkpolice-catalog.pages.dev/api-docs.html
