# Phân trang

Endpoint danh sách trả kết quả từng trang, mới nhất trước, theo kiểu **phân trang theo khoá (keyset)**: bạn xin trang nằm ngay sau phần tử cuối cùng bạn đang có. Khác với số trang, cách này vẫn đúng khi phần tử mới liên tục được thêm vào. Trang này giải thích tham số, các trường trong response và cách duyệt hết một danh sách.

## Tham số

| Tham số | Ý nghĩa |
|---|---|
| `limit` | Số phần tử cần trả. Giá trị mặc định và tối đa tuỳ danh sách (xem bên dưới). Giá trị ngoài khoảng nhận `422 validation_error`. |
| `after` | `id` của phần tử cuối ở trang trước. Response bắt đầu ngay sau phần tử đó. Bỏ trống ở trang đầu. |

`GET /emails` nhận thêm `before`, là `id` của một phần tử, để lấy các phần tử mới hơn nó.

## Response

Mọi danh sách trả về cùng một dạng:

```json
{
  "object": "list",
  "has_more": true,
  "next_cursor": "pay_01a0b904297873e8b3524a26fdca0ab5",
  "data": [
    { "object": "payment", "id": "pay_01a0b904...", "status": "paid" },
    { "object": "payment", "id": "pay_01a0b903...", "status": "pending" }
  ]
}
```

| Trường | Ý nghĩa |
|---|---|
| `data` | Các phần tử của trang này |
| `has_more` | `true` khi còn ít nhất một trang nữa |
| `next_cursor` | Chỉ có ở danh sách của Thanh toán: giá trị truyền vào `after` để lấy trang tiếp, hoặc `null` ở trang cuối. Đó là `id` của phần tử cuối trong `data`. |

Với danh sách không có `next_cursor`, truyền `id` của phần tử cuối trong `data` vào `after`. Kết quả như nhau.

## Giới hạn theo từng danh sách

| Danh sách | `limit` mặc định | Tối đa |
|---|---|---|
| Email: email, thư đến, liên hệ, template, logs | 20 | 100 |
| Thanh toán: thanh toán, giao dịch, hoá đơn điện tử | 20 | 100 |
| Hỗ trợ: hội thoại | 50 | 100 |
| Hỗ trợ, Sản phẩm và CRM: phiếu hỗ trợ, sản phẩm, đơn hàng, khách hàng | 50 | 200 |

Một vài danh sách nhỏ như tên miền, webhook và chiến dịch email trả mọi phần tử trong một response.

Tin nhắn của hội thoại phân trang theo cách khác, bằng số thứ tự: `GET /chat/conversations/{id}/messages` nhận `after_seq` (`seq` của tin nhắn cuối bạn đang có) và `limit` tối đa 200, mặc định 100. Đây là cách đồng bộ một hội thoại theo từng phần mới.

## Duyệt hết một danh sách

Lấy một trang, xử lý, rồi tiếp tục khi `has_more` còn là `true`:

**cURL**

```bash
# Trang đầu
curl "https://api.hatang.dev/payments?limit=100" \
  -H "Authorization: Bearer $HATANG_API_KEY"

# Trang tiếp: truyền next_cursor (hoặc id cuối) vào after
curl "https://api.hatang.dev/payments?limit=100&after=pay_01a0b904297873e8b3524a26fdca0ab5" \
  -H "Authorization: Bearer $HATANG_API_KEY"
```

**Node.js**

```js
import { Hatang } from "@hatang/sdk";

const hatang = new Hatang(process.env.HATANG_API_KEY, { baseUrl: "https://api.hatang.dev" });

async function* allEmails() {
  let after;
  while (true) {
    const { data: page, error } = await hatang.emails.list({ limit: 100, after });
    if (error) throw new Error(`${error.name}: ${error.message}`);
    yield* page.data;
    if (!page.has_more || page.data.length === 0) return;
    after = page.data[page.data.length - 1].id;
  }
}

for await (const email of allEmails()) {
  console.log(email.id, email.last_event);
}
```

**Python**

```python
from hatang import Hatang

hatang = Hatang()  # đọc HATANG_API_KEY

def all_payments():
    after = None
    while True:
        page = hatang.payments.list(limit=100, after=after)
        yield from page["data"]
        if not page["has_more"]:
            return
        after = page.get("next_cursor") or page["data"][-1]["id"]

for payment in all_payments():
    print(payment["id"], payment["status"])
```

**PHP**

```php
function allConversations(GuzzleHttp\Client $client): Generator {
    $query = ['limit' => 100];
    do {
        $page = json_decode((string) $client->get('/chat/conversations', ['query' => $query])->getBody(), true);
        yield from $page['data'];
        $last = end($page['data']);
        $query['after'] = $last ? $last['id'] : null;
    } while ($page['has_more'] && $last);
}

foreach (allConversations($client) as $conversation) {
    echo $conversation['id'], PHP_EOL;
}
```

> **Tip**
>
> Bộ lọc như `status` giữ nguyên ở mọi trang: gửi kèm trong từng request cùng với `after`. Mỗi trang là một request tính vào [giới hạn tần suất](https://hatang.dev/vi/docs/guides/getting-started/rate-limits.md), nên hãy dùng `limit` lớn nhất mà bạn xử lý được.

## Bước tiếp theo

- [Giới hạn tần suất](https://hatang.dev/vi/docs/guides/getting-started/rate-limits.md)
- [Lỗi](https://hatang.dev/vi/docs/guides/getting-started/errors.md)
- [Tham chiếu REST API](https://hatang.dev/vi/docs/reference/api.md)

Source: https://hatang.dev/vi/docs/guides/getting-started/pagination
