# 文本检测

> 通过 POST /text/detections 将纯文本判定为 AI 撰写、混合或人类撰写。内容包括请求和响应字段、段落偏移量、限制与计费。

来源： https://fileverity.com/zh/docs/text-detections

`POST /text/detections` 运行一次文本检测。它使用 `fvdoc-3-text` 对纯文本分类，并返回其中由 AI 撰写的段落。该接口是同步的：结果就在响应中。

示例从文件中读取文本，因此请先把任意一段不少于 100 个词的文字保存为 `essay.txt`。[快速入门](https://fileverity.com/zh/docs/quickstart.md#text)中提供了写入示例文本的命令。

**请求: 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)"
```

**请求: 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();
```

**请求: 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()
```

## 请求体

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `text` | string | 是 | 纯文本，至少 100 个词，最多 100,000 个字符。 |
| `model` | string | 否 | 默认值为 `fvdoc-3-text`。可传入模型 ID 或其快照 ID `fvdoc-3-text-2026-11-03`。 |
| `language` | string 或 null | 否 | ISO 639-1 语言代码：`en`、`fr`、`de`、`es`、`zh`。省略时自动检测。 |

未知字段会被拒绝并返回 `invalid_parameter`。校验至少 100 个词的要求时，汉字约每两个计为一个词。

## 响应

**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
  }
}
```

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | string | 检测 ID，前缀为 `tdet_`。 |
| `object` | string | 始终为 `text.detection`。 |
| `created` | integer | Unix 时间戳。 |
| `model` | string | 生成该结果的快照。 |
| `livemode` | boolean | 测试密钥为 `false`。 |
| `language` | string | 实际使用的语言：您传入的语言，或自动检测到的语言。 |
| `verdict` | string | `ai`：全文读起来像 AI 撰写。`mixed`：人类撰写的文本中夹有 AI 撰写的段落。`human`：未发现 AI 撰写的段落。 |
| `score` | number | 取值 0 到 1。模型对最像 AI 撰写的段落给出的分数。 |
| `ai_fraction` | number | 取值 0 到 1。被评估文本中落在 AI 撰写段落内的占比。 |
| `segments` | array | AI 撰写的段落。每个段落包含 `start`、`end`（不含）和 `score`。 |
| `usage` | object | 已评估的 `characters`，由免费额度覆盖的 `free_characters`，以及扣除免费字符后实际计费的 `billed_units`。 |

> **注意。** 文本检测返回的是统计估计，而不是作者身份的证明。段落边界是近似的。请将其作为人工复核的参考依据之一，不要单独依据它作出影响个人的决定。

## 段落偏移量

`start` 和 `end` 是所提交文本中的偏移量，按 Unicode 码点计数，既不是字节数，也不是 UTF-16 码元数。段落边界是近似的，可能包含相邻的句子。JavaScript 字符串按 UTF-16 索引，因此请先拆分为码点：

**截取一个段落: JavaScript**

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

**截取一个段落: Python**

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

Python 字符串本身就按码点索引。

## 计费

- 文本按每 1,000 字符 US$0.14 计费，按 Unicode 码点计数。每次请求向上取整为整数个计费单位，至少计一个单位。
- 免费额度中的文本字符会先被使用，并在 `usage.free_characters` 中报告。`usage.billed_units` 是扣除它们之后实际计费的单位数，因此费用为 `billed_units` 乘以 US$0.14。例如：1,412 个字符且已无剩余免费字符时，计 2 个单位，即 US$0.28。
- 在评估之前被拒绝的请求不收费，例如参数无效、文本过短或过长、语言不受支持等情况。在服务内部失败的检测（`service_unavailable`）会释放已预留的费用，不收费。
- 余额不足时返回 `insufficient_balance`（402）。

## 存储的内容

文本在评估后即被丢弃，从不存储，因此之后无法再获取。

Fileverity 会保留不含文本的结果（判定、分数、段落偏移量和用量）7 天，以便控制台显示，同时用于用量计费和审计。使用相同 `Idempotency-Key` 重试的请求，在 24 小时内会得到已保存的结果。文本检测是同步的：它们不会出现在 `GET /analyses` 中，也不会发送 Webhook 事件。测试密钥不记录任何内容。参见[数据保留与安全](https://fileverity.com/zh/docs/security.md)。

## 错误

| 错误码 | 状态 | 原因 |
| --- | --- | --- |
| `text_too_short` | 400 | 少于 100 个词。 |
| `text_too_long` | 413 | 超过 100,000 个字符。请把文本拆分为多个请求。 |
| `unsupported_language` | 400 | 语言不受支持，或无法检测。 |
| `unsupported_media_type` | 415 | 请求体不是以 `application/json` 发送的。 |
| `invalid_json` | 400 | 请求体不是 JSON 对象。 |
| `missing_parameter` | 400 | 缺少 `text` 字段。 |
| `invalid_parameter` | 400 | 字段类型错误、发送了未知字段，或 `Idempotency-Key` 无效。 |
| `model_not_found` | 404 | `model` 未知。 |
| `insufficient_balance` | 402 | 余额不足以支付该请求。 |
| `spend_limit_reached` | 402 | 密钥已达到每月支出上限。 |
| `idempotency_mismatch`, `idempotency_in_progress` | 409 | 该 `Idempotency-Key` 曾用于不同的请求体，或其首次请求仍在运行。 |
| `service_unavailable`, `analysis_failed` | 503, 502 | 检测服务暂时不可用或繁忙。未扣费，请重试。`service_unavailable` 会带有 `retry-after` 响应头。 |

> **注意。** 请发送 `Idempotency-Key`，这样超时后重试就不会重复计费。参见[幂等性](https://fileverity.com/zh/docs/idempotency.md)。

