# Phát hiện văn bản

> Phân loại văn bản thuần túy là do AI viết, do AI viết một phần hoặc do người viết bằng POST /text/detections. Các trường yêu cầu và phản hồi, offset của các đoạn, giới hạn và cách tính phí.

Nguồn: https://fileverity.com/vi/docs/text-detections

`POST /text/detections` chạy một lượt kiểm tra văn bản. Endpoint này phân loại văn bản thuần túy bằng `fvdoc-3-text` và trả về các đoạn do AI viết. Đây là lệnh gọi đồng bộ: kết quả nằm ngay trong phản hồi.

Các ví dụ đọc văn bản từ một tệp, vì vậy trước tiên hãy lưu một đoạn văn bất kỳ có từ 100 từ trở lên thành `essay.txt`. Phần [Bắt đầu nhanh](https://fileverity.com/vi/docs/quickstart.md#text) có một lệnh ghi ra văn bản mẫu.

**Yêu cầu: cURL**

```bash
curl https://api.fileverity.com/v1/text/detections \
  -H "Authorization: Bearer $FILEVERITY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6b9d1c1e-3b0f-4c0a-9a53-2d1c9d6b7e11" \
  -d "$(jq -Rs '{text: ., language: "en"}' essay.txt)"
```

**Yêu cầu: JavaScript**

```javascript
import { readFile } from "node:fs/promises";
import { randomUUID } from "node:crypto";

const response = await fetch("https://api.fileverity.com/v1/text/detections", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.FILEVERITY_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": randomUUID(),
  },
  body: JSON.stringify({ text: await readFile("essay.txt", "utf8"), language: "en" }),
});
const detection = await response.json();
```

**Yêu cầu: Python**

```python
import os
import uuid
import requests

response = requests.post(
    "https://api.fileverity.com/v1/text/detections",
    headers={
        "Authorization": "Bearer " + os.environ["FILEVERITY_API_KEY"],
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"text": open("essay.txt", encoding="utf-8").read(), "language": "en"},
)
detection = response.json()
```

## Nội dung yêu cầu

| Trường | Kiểu | Bắt buộc | Mô tả |
| --- | --- | --- | --- |
| `text` | string | Có | Văn bản thuần túy có ít nhất 100 từ và tối đa 100.000 ký tự. |
| `model` | string | Không | Mặc định là `fvdoc-3-text`. Chấp nhận mã mô hình hoặc mã snapshot `fvdoc-3-text-2026-11-03` của mô hình. |
| `language` | string hoặc null | Không | Mã ISO 639-1: `en`, `fr`, `de`, `es`, `zh`. Tự động nhận diện nếu không truyền. |

Các trường không xác định bị từ chối với `invalid_parameter`. Khi kiểm tra mức tối thiểu 100 từ, khoảng hai ký tự tiếng Trung được tính là một từ.

## Phản hồi

**200 OK**

```json
{
  "id": "tdet_8Hk2pQ4vN9sL1xR7cW3mY6zB",
  "object": "text.detection",
  "created": 1793664000,
  "model": "fvdoc-3-text-2026-11-03",
  "livemode": true,
  "language": "en",
  "verdict": "mixed",
  "score": 0.97,
  "ai_fraction": 0.41,
  "segments": [
    {
      "start": 212,
      "end": 433,
      "score": 0.97
    },
    {
      "start": 579,
      "end": 790,
      "score": 0.93
    }
  ],
  "usage": {
    "characters": 1412,
    "free_characters": 0,
    "billed_units": 2
  }
}
```

| Trường | Kiểu | Mô tả |
| --- | --- | --- |
| `id` | string | Mã lượt phát hiện, có tiền tố `tdet_`. |
| `object` | string | Luôn là `text.detection`. |
| `created` | integer | Dấu thời gian Unix. |
| `model` | string | Snapshot đã tạo ra kết quả. |
| `livemode` | boolean | `false` với khóa thử nghiệm. |
| `language` | string | Ngôn ngữ được dùng: ngôn ngữ bạn đã gửi, hoặc ngôn ngữ được nhận diện. |
| `verdict` | string | `ai`: toàn bộ văn bản có đặc điểm của văn bản do AI viết. `mixed`: có các đoạn do AI viết nằm trong văn bản do người viết. `human`: không tìm thấy đoạn nào do AI viết. |
| `score` | number | Từ 0 đến 1. Điểm của mô hình cho đoạn giống văn bản do AI viết nhất. |
| `ai_fraction` | number | Từ 0 đến 1. Tỷ lệ văn bản được đánh giá nằm trong các đoạn do AI viết. |
| `segments` | array | Các đoạn do AI viết. Mỗi đoạn có `start`, `end` (không bao gồm) và `score`. |
| `usage` | object | Số `characters` đã được đánh giá, số `free_characters` được lượt dùng thử miễn phí của bạn chi trả và số `billed_units` thực tế bị tính phí sau khi trừ các ký tự miễn phí. |

> **Lưu ý.** Phát hiện văn bản trả về một ước tính thống kê, không phải bằng chứng xác định ai là người viết. Ranh giới các đoạn chỉ mang tính tương đối. Hãy dùng kết quả như một đầu vào cho việc xem xét của con người, và đừng chỉ dựa vào kết quả này để ra quyết định ảnh hưởng đến một người.

## Offset của các đoạn

`start` và `end` là offset (vị trí) trong văn bản bạn đã gửi, được đếm theo code point Unicode, không phải byte và không phải đơn vị UTF-16. Ranh giới các đoạn chỉ mang tính tương đối và có thể bao gồm cả các câu liền kề. Chuỗi trong JavaScript được đánh chỉ mục theo UTF-16, vì vậy hãy tách thành các code point trước:

**Cắt một đoạn: JavaScript**

```javascript
const points = Array.from(text);
for (const { start, end } of detection.segments) {
  console.log(points.slice(start, end).join(""));
}
```

**Cắt một đoạn: Python**

```python
for segment in detection["segments"]:
    print(text[segment["start"]:segment["end"]])
```

Chuỗi trong Python vốn đã được đánh chỉ mục theo code point.

## Tính phí

- Văn bản được tính phí theo mỗi 1.000 ký tự với giá 0,14 USD, đếm theo code point Unicode. Mỗi yêu cầu được làm tròn lên thành số đơn vị nguyên, tối thiểu một đơn vị.
- Các ký tự văn bản miễn phí trong lượt dùng thử của bạn được dùng trước và được báo cáo trong `usage.free_characters`. `usage.billed_units` là số đơn vị bị tính phí sau đó, vì vậy chi phí bằng `billed_units` nhân 0,14 USD. Ví dụ: 1.412 ký tự khi không còn ký tự miễn phí được tính 2 đơn vị, tức 0,28 USD.
- Các yêu cầu bị từ chối trước khi đánh giá không bị tính phí: tham số không hợp lệ, văn bản quá ngắn hoặc quá dài, ngôn ngữ không được hỗ trợ và các trường hợp tương tự. Lượt kiểm tra thất bại bên trong dịch vụ (`service_unavailable`) được hoàn lại và không bị tính phí.
- Số dư không đủ sẽ trả về `insufficient_balance` (402).

## Dữ liệu được lưu

Văn bản được đánh giá rồi xóa ngay. Văn bản không bao giờ được lưu, nên không thể lấy lại sau này.

Fileverity giữ kết quả không kèm văn bản (kết luận, điểm số, offset của các đoạn và mức sử dụng) trong 7 ngày, để Console có thể hiển thị kết quả đó và mức sử dụng có thể được tính phí và đối soát. Yêu cầu được gửi lại với cùng `Idempotency-Key` sẽ nhận kết quả đã lưu trong 24 giờ. Lượt phát hiện văn bản là đồng bộ: chúng không xuất hiện trong `GET /analyses` và không gửi sự kiện webhook. Khóa thử nghiệm không ghi lại gì. Xem [Lưu giữ dữ liệu và bảo mật](https://fileverity.com/vi/docs/security.md).

## Lỗi

| Mã lỗi | Trạng thái | Nguyên nhân |
| --- | --- | --- |
| `text_too_short` | 400 | Ít hơn 100 từ. |
| `text_too_long` | 413 | Nhiều hơn 100.000 ký tự. Hãy chia văn bản thành nhiều yêu cầu. |
| `unsupported_language` | 400 | Ngôn ngữ không được hỗ trợ, hoặc không thể nhận diện được. |
| `unsupported_media_type` | 415 | Nội dung yêu cầu không được gửi dưới dạng `application/json`. |
| `invalid_json` | 400 | Nội dung yêu cầu không phải là một đối tượng JSON. |
| `missing_parameter` | 400 | Thiếu trường `text`. |
| `invalid_parameter` | 400 | Một trường có kiểu sai, có trường không xác định được gửi lên, hoặc `Idempotency-Key` không hợp lệ. |
| `model_not_found` | 404 | `model` không tồn tại. |
| `insufficient_balance` | 402 | Số dư không đủ để thanh toán cho yêu cầu. |
| `spend_limit_reached` | 402 | Khóa đã đạt hạn mức chi tiêu hằng tháng. |
| `idempotency_mismatch`, `idempotency_in_progress` | 409 | `Idempotency-Key` đã được dùng với nội dung yêu cầu khác, hoặc yêu cầu đầu tiên của khóa này vẫn đang chạy. |
| `service_unavailable`, `analysis_failed` | 503, 502 | Dịch vụ phát hiện tạm thời không khả dụng hoặc đang bận. Không bị tính phí; hãy thử lại. `service_unavailable` kèm theo header `retry-after`. |

> **Lưu ý.** Hãy gửi `Idempotency-Key` để việc thử lại sau khi hết thời gian chờ không bị tính phí hai lần. Xem [Idempotency](https://fileverity.com/vi/docs/idempotency.md).

