# 文件

> 向 Fileverity 上传图片和文档：4 MB 以内的文件使用 multipart，50 MB 以内的文件使用预签名上传。内容包括文件对象、限制与保留期。

来源： https://fileverity.com/zh/docs/files

图片和文档的检测分两步。首先用 `POST /files` 上传文件，获得文件 ID；然后创建引用该 ID 的[分析](https://fileverity.com/zh/docs/analyses.md)。文本不需要文件，请使用[文本检测](https://fileverity.com/zh/docs/text-detections.md)。

## 支持的文件

|  | 图片（fvimg-2） | 文档（fvdoc-3） |
| --- | --- | --- |
| 格式 | `jpeg`, `png`, `webp` | `pdf`, `docx`, `doc`, `rtf`, `odt`, `pptx`, `txt` |
| 文件大小上限 | 10 MB | 50 MB |
| 其他限制 | 4000 万像素 | 100 页 |

## 两种上传方式

| 方式 | 请求 | 大小 | 响应中的文件状态 |
| --- | --- | --- | --- |
| Multipart | `multipart/form-data`，字段为 `file` | 最大 4 MB | `uploaded` |
| 预签名 URL | 包含 `filename`、`content_type` 和 `bytes` 的 JSON，随后用 `PUT` 上传文件内容 | 最大 50 MB | 在文件内容到达之前为 `pending` |

无论使用哪种方式，图片都受上述图片大小限制。

## Multipart 上传

4 MB 以内的文件以 `multipart/form-data` 形式通过字段 `file` 发送。文件格式根据文件内容检测，而不是根据文件名或声明的类型。

**Multipart 上传: cURL**

```bash
curl https://api.fileverity.com/v1/files \
  -H "Authorization: Bearer $FILEVERITY_API_KEY" \
  -F file=@claim-photo.jpg
```

**Multipart 上传: JavaScript**

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

const form = new FormData();
form.append("file", new Blob([await readFile("claim-photo.jpg")]), "claim-photo.jpg");
const response = await fetch("https://api.fileverity.com/v1/files", {
  method: "POST",
  headers: { Authorization: "Bearer " + process.env.FILEVERITY_API_KEY },
  body: form,
});
const file = await response.json();
```

**Multipart 上传: Python**

```python
import os
import requests

response = requests.post(
    "https://api.fileverity.com/v1/files",
    headers={"Authorization": "Bearer " + os.environ["FILEVERITY_API_KEY"]},
    files={"file": open("claim-photo.jpg", "rb")},
)
file = response.json()
```

**201 Created**

```json
{
  "id": "file_8Hk2pQ4vN9sL1xR7cW3mY6zB",
  "object": "file",
  "filename": "claim-photo.jpg",
  "bytes": 2481920,
  "content_type": "image/jpeg",
  "status": "uploaded",
  "livemode": true,
  "created": 1793700000,
  "expires_at": 1793786400
}
```

## 预签名上传

对于大于 4 MB 且不超过 50 MB 的文件，先申请上传 URL，再把文件内容直接发送到存储服务。

1. 使用 JSON 请求体调用 `POST /files`：包含 `filename`（可选）、`content_type` 和以 `bytes` 表示的精确大小。响应中包含 `status: pending` 和 `upload` 对象。
2. 在 `upload.expires_at` 之前，使用 `upload.headers` 中的请求头，将文件内容 `PUT` 到 `upload.url`。此请求不需要 `Authorization` 请求头。
3. 可选：轮询 `GET /files/{id}`，直到 `status` 变为 `uploaded`。
4. 使用该文件 ID 创建分析。

**预签名上传: cURL**

```bash
FILE=$(curl -s https://api.fileverity.com/v1/files \
  -H "Authorization: Bearer $FILEVERITY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filename": "contract.pdf", "content_type": "application/pdf", "bytes": 18874368}')

UPLOAD_URL=$(echo "$FILE" | jq -r .upload.url)
FILE_ID=$(echo "$FILE" | jq -r .id)

curl -X PUT "$UPLOAD_URL" \
  -H "content-type: application/pdf" \
  --data-binary @contract.pdf
```

**预签名上传: JavaScript**

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

const bytes = await readFile("contract.pdf");
const response = await fetch("https://api.fileverity.com/v1/files", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.FILEVERITY_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ filename: "contract.pdf", content_type: "application/pdf", bytes: bytes.length }),
});
const file = await response.json();

await fetch(file.upload.url, { method: "PUT", headers: file.upload.headers, body: bytes });
```

**预签名上传: Python**

```python
import os
import requests

data = open("contract.pdf", "rb").read()
response = requests.post(
    "https://api.fileverity.com/v1/files",
    headers={"Authorization": "Bearer " + os.environ["FILEVERITY_API_KEY"]},
    json={"filename": "contract.pdf", "content_type": "application/pdf", "bytes": len(data)},
)
file = response.json()

requests.put(file["upload"]["url"], headers=file["upload"]["headers"], data=data)
```

**201 Created**

```json
{
  "id": "file_Pw3mY6zB8Hk2pQ4vN9sL1xR7",
  "object": "file",
  "filename": "contract.pdf",
  "bytes": 18874368,
  "content_type": "application/pdf",
  "status": "pending",
  "livemode": true,
  "created": 1793700000,
  "expires_at": 1793786400,
  "upload": {
    "url": "https://example.private.blob.vercel-storage.com/f/0d6f1c2e-7b1a-4c53-9a0e-5e8a41b3c7d2?signature=...",
    "method": "PUT",
    "headers": {
      "content-type": "application/pdf"
    },
    "expires_at": 1793703600
  }
}
```

此处显示的 URL 已缩短。请原样使用响应中的 URL。`content-type` 请求头必须与 `upload.headers` 中返回的完全一致。

### 接受的内容类型

| 类别 | 内容类型 |
| --- | --- |
| 图片 | `image/jpeg`, `image/png`, `image/webp` |
| 文档 | `application/pdf`, `application/vnd.openxmlformats-officedocument.wordprocessingml.document`, `application/msword`, `application/rtf`, `application/vnd.oasis.opendocument.text`, `application/vnd.openxmlformats-officedocument.presentationml.presentation`, `text/plain` |

## 文件对象

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `id` | string | 文件 ID，前缀为 `file_`。 |
| `object` | string | 始终为 `file`。 |
| `filename` | string 或 null | 经过安全处理的文件名。 |
| `bytes` | integer 或 null | 大小，单位为字节。 |
| `content_type` | string | 检测到的或声明的内容类型。 |
| `status` | string | 上传的内容到达之前为 `pending`，到达后为 `uploaded`。 |
| `livemode` | boolean | 使用测试密钥创建的文件，该值为 `false`。文件按模式隔离。 |
| `created` | integer | Unix 时间戳。 |
| `expires_at` | integer | 文件将在此 Unix 时间戳之后被删除。 |
| `upload` | object | 仅出现在 JSON 上传请求的响应中：`url`、`method`（`PUT`）、`headers` 和 `expires_at`。 |

## 保留期

- 文件最多保留 24 小时（见 `expires_at`），之后会被删除。
- 一旦基于某个文件开始分析，该文件就会被立即删除。每个上传的文件只能用于一次分析；要再次检测，请重新上传。
- `DELETE /files/{id}` 可提前删除文件及其已存储的内容。
- 每个工作区在每种模式下，同时最多可保留 50 个未过期的文件和 200 MB 数据。超出后，上传会失败并返回 `pending_files_limit`（HTTP 429，不带 `retry-after`），直到文件被使用、删除或过期。
- 未知、已过期或已被使用的文件 ID，在创建分析时返回 `file_not_found`，在获取或删除时返回 `not_found`。向 `upload.url` 的上传尚未完成时，也会返回 `file_not_found`，因此请等到 `GET /files/{id}` 显示 `uploaded`。

## 错误

| 错误码 | 状态 | 原因 |
| --- | --- | --- |
| `missing_parameter` | 400 | multipart 请求体中没有 `file` 字段。 |
| `unsupported_media_type` | 415 | 请求体既不是 `multipart/form-data`，也不是 JSON。 |
| `unsupported_file` | 415 | 不支持该格式。 |
| `invalid_file` | 422 | 文件为空、已损坏或无法读取。 |
| `file_too_large` | 413 | multipart 文件大于 4 MB，或 `bytes` 超过 50 MB。 |
| `invalid_parameter` | 400 | JSON 字段缺失、类型错误或未知。 |
| `invalid_json` | 400 | JSON 请求体不是对象。 |
| `pending_files_limit` | 429 | 工作区中未过期的上传过多。不附带 `retry-after` 响应头。 |
| `idempotency_mismatch`, `idempotency_in_progress` | 409 | 该 `Idempotency-Key` 曾用于不同的请求，或其首次请求仍在运行。 |

> **注意。** 测试密钥会校验上传，但不存储文件内容。使用测试密钥发出的 JSON 上传请求会返回测试上传对象（`upload.test` 为 `true`，文件已是 `uploaded`）：无需再执行 PUT。

