> ## Agent Instructions
>
> Base URL: https://api.anysite.io
> Authentication: send the `access-token` header. Do NOT use `Authorization: Bearer`.
> Full endpoint catalog: https://app.anysite.io/docs
# 错误与限制

Anysite API 可能返回的每一种响应、每种状态的含义、计费规则、超时、分页和速率限制。

目标：正确处理 API 可能返回的每一种响应，并清楚哪些调用会消耗积分。

## 成功的响应

成功的数据调用响应体**始终是 JSON 数组**，即使端点返回的是单个实体 —— `linkedin/user` 返回的是包含
一个元素的列表。没有匹配结果的搜索是正常的 `200`，响应体为 `[]`。

应从 `x-result-count` 响应头读取结果数量，而不是数组长度：如果 `x-result-count` 较大而响应体为空，
说明响应被截断了，而不是没有结果。

## 一次调用是如何被处理的

检查按以下顺序执行；第一个未通过的检查会返回其错误，且不计费：

1. 身份验证请求头存在且只有一个 → 否则返回 `422`。
2. 密钥有效、套餐未到期、密钥允许用于 REST API → 否则返回 `401` / `403`。
3. 端点在你的套餐中可用，且参数不超过套餐允许的最大值 → 否则返回 `404` / `529` / `403` / `400`。
4. 余额中有足够的积分 → 否则返回 `401`。
5. 速率限制和用量窗口 → 否则返回 `429`。
6. 请求在端点上执行；其响应会返回给你，并按 [What is charged](#what-is-charged) 中的规则计费。

## 调用执行前返回的状态

以下状态来自 Anysite 的计费层，在到达端点之前返回。这些响应均不计费，响应体的结构是
`{"detail": ...}`：

```json
{"detail": "Token expired"}
```

`422` 返回的是字段错误列表，`429` 返回一个对象，并附带一个值相同（单位：秒）的 `X-Retry-After`
响应头：

```json
{"detail": {"message": "Rate limit exceeded: ...", "retry_after": 12}}
```

| 状态码 | `detail` | 该怎么做 |
|---|---|---|
| `422` | 缺少必需的请求头 | 发送 `access-token` 请求头。 |
| `422` | `Body should be a valid dict object` | 请求体应为 JSON 对象，而不是数组或标量值。 |
| `401` | `Invalid token` | 从「订阅与 API」页面重新复制密钥。参见[身份验证](/docs/authentication?lang=zh)。 |
| `401` | `Token does not exist` | 密钥已被删除；请使用另一个密钥。 |
| `401` | `Token expired` | 套餐已到期；请在「订阅与 API」页面续订。 |
| `401` | `Points limit exhausted, required at least N points` | 购买更多积分或升级套餐。参见[套餐与积分](/docs/plans-and-credits?lang=zh)。 |
| `403` | `This token is restricted to MCP usage only and cannot be used for direct API access` | 改用 [MCP 服务器](/docs/mcp/overview?lang=zh)，或选择一个包含 REST API 的套餐。 |
| `403` | `This endpoint is disabled for your plan` | 选择一个包含此端点的套餐。 |
| `400` | `Parameter 'count' exceeds the maximum allowed for your plan: N` | 把 `count` 降到 N 或更低，或分页获取结果。 |
| `404` | `Endpoint was removed` | 该端点已不存在；请在 [API 参考](/docs/api)中查找替代端点。 |
| `529` | `This endpoint is temporarily unavailable while we work on a fix. ...` | 该端点已被我们临时关闭。重试无济于事；如果你依赖它，请从「支持」页面提交工单。 |
| `429` | object, `Rate limit exceeded: scope=..., limit=N requests <window>. ...` | 等待 `retry_after` 秒。参见 [Rate limits](#rate-limits)。 |
| `429` | object, `You've reached your <window> usage limit. ...` | 等待窗口释放额度，或升级套餐。参见 [Usage windows](#usage-windows)。 |

`429` 一定来自 Anysite 的速率限制器 —— 端点背后的数据源本身永远不会产生这个状态码。

## Statuses from the endpoint

通过检查之后，端点自身的状态码和响应体会原样返回给你。**端点的错误响应体仍然是空的 JSON 数组
`[]`；原因在 `x-error` 响应头中**，该响应头只有在状态码为 `400` 或更高时才会出现：

```
HTTP/1.1 412
x-error: Failed to fetch URL. Website is not accessible
x-result-count: 0

[]
```

验证错误是例外：格式错误的请求体会被 `422` 拒绝，并返回一个指明具体字段的 `detail` 对象。

| 状态码 | 含义 |
|---|---|
| `200` | 成功。响应体是一个数组，可能为空。 |
| `209` | 查找了但没有结果，且本次查找没有产生费用 —— 一次免费的未命中。仅出现在 [What is charged](#what-is-charged) 中列出的端点上。 |
| `400` | 客户端在结果准备好之前断开了连接，或者 LinkedIn URN 格式有误，且验证未能捕获这种错误。 |
| `408` | 调用超时。参见 [Timeouts](#timeouts)。响应体中仍可能包含截止前已收集到的数据行。 |
| `412` | API 的「未找到」：实体不存在、资料是私密的或被限制访问，或者（在 `webparser/*` 上）目标主机不可达。 |
| `415` | `webparser/*` 指向的是 PDF、Office 文档、图片、压缩包或其他非页面文件。 |
| `422` | 模式验证失败（`detail` 中指明字段）、请求体无效，或响应过大而无法返回。 |
| `500` | 数据源无法访问，或对它的重试已耗尽。 |
| `529` | 该数据源目前的抓取能力全部繁忙。 |

[API 参考](/docs/api)中每个端点只列出它**不那么显而易见**的状态码 —— 例如 `linkedin/user`
会返回 `412`，而不是你可能预期的 `404`。列表中没出现的状态码并非不可能出现；上面的含义始终适用。

被限制访问的 LinkedIn 资料，只要仍处于限制状态就会一直返回 `412` —— 用同一个别名重试不会改变结果。

## What is charged

简而言之：

- `200` 会计费，**包括返回空数组的搜索**。你支付的是查找本身的费用，而不是结果条数。
- `412` —— 「未找到」的回答 —— **会计费**。查找的工作已经完成。
- `408` 超时会计费，且响应体中仍可能包含部分数据行。
- `209` **不计费**。它只出现在未命中不会产生成本的端点上：`linkedin/search/sql/users`、
  `linkedin/search/sql/companies`、`crunchbase/db/search`、`linkedin/user/email`、
  `linkedin/user/find_email_by_url`、`person/by_email`、`person/by_phone`、`phone/find`、
  `email/find`、`company/resolve`。在这些端点上，如果付费数据供应商已经给出过未命中的答案，结果
  仍然是 `412` 并计费 —— 你无法预先知道会得到哪一种，所以要读取 `x-credits-charged`。
- 其他任何错误 —— `400`、`415`、`422`、`500`、`529` —— **仅当响应仍然带回了结果时才计费**。带回
  数据行的失败是一次付费调用，同样的失败但响应体为空则免费。响应头 `x-result-count` 会告诉你属于
  哪一种。
- 请求根本没有到达端点时不计费：上述各项检查，以及端点完全无法访问时我们返回的
  `{"error": "Internal server error"}`。

| Endpoint response | Charged |
|---|---|
| 低于 `400` 的任意状态码（`209` 除外） | 计费 |
| `209` | 不计费 |
| `408`、`412` | 计费 |
| 其他任意 `4xx` / `5xx` | 仅当响应报告了至少一条结果时计费 |
| 上述检查产生的任何错误，或端点不可达时返回的 `500`「Internal server error」 | 不计费 |

调用的价格取决于端点和你的套餐，并且在许多端点上会随结果数量增长：每开始一个结果区块就按基础价格
计费一次，因此按每 10 条结果计价的端点，返回 11 条时收费两次，返回 21 条时收费三次。这两个数字都
按端点发布在 [API 参考](/docs/api)中：`x-price` 是基础价格，`x-increase-every` 是区块大小（没有该
字段表示每次调用一口价）。只请求你真正需要的 `count` —— `count` 越大，可能越贵。每次调用的精确
金额在 `x-credits-charged` 响应头中 —— `0` 表示本次调用免费 —— 以及「订阅与 API」页面的请求日志里。

积分优先从套餐积分中扣除，然后才扣除已购买积分。只要余额能覆盖基础价格，调用就会被允许；如果
最终价格更高（例如返回结果更多），余额可能变为负数，负的部分会从你之后追加的积分中扣除。

## 值得关注的响应头

| 响应头 | 含义 |
|---|---|
| `x-credits-charged` | 本次调用花费的积分。`0` 表示免费。 |
| `x-token-points` | 余额中剩余的积分。 |
| `x-result-count` | 响应中的结果行数。如果比你收到的数组长度大 → 说明响应被截断了。 |
| `x-error` | 调用失败的原因。仅当状态码为 `400` 或更高时出现。 |
| `x-warning` | 请求虽然成功，但存在一些问题 —— 参见 [Ignored parameters](#ignored-parameters)。 |
| `x-execution-time` | 端点处理耗时，单位为秒。 |
| `x-request-id` | 本次调用的 id。在支持工单中引用它。你也可以自己指定它，但必须是合法的 UUID，否则请求会被 `422` 拒绝。 |
| `X-Total-Available-Results` | 在数据源提供可用总数的端点上，总共可获取的结果数量。 |

## Timeouts

请求体中的 `timeout` 字段默认值为 **300 秒**，可接受 20-1500 之间的值。

它是**停滞预算，而不是总时长**：衡量的是调用在多长时间内没有产生新结果。每收到一条新数据，
截止时间就会向后推移，直到 1500 秒的硬上限为止。因此，一次持续有进展的调用完全可能比它的 `timeout`
运行得更久，而一旦停滞的调用在 `timeout` 时间内没有进展，就会被 `408` 中断。

由于这个上限的存在，对于可能长时间运行的端点，**请把你的 HTTP 客户端自身的超时设置为约 1600
秒**。如果客户端在 30 秒或 60 秒就放弃，会中断那些其实仍在正常工作的调用。

```bash
curl --max-time 1600 -X POST "https://api.anysite.io/api/linkedin/search/users" \
  -H "access-token: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"keywords": "cto", "count": 100, "timeout": 600}'
```

> ACTION FOR THE HUMAN: copy your API key at https://app.anysite.io/billing — an agent cannot obtain it.

## Ignored parameters

**请求体中无法识别的字段会被忽略，而不是拒绝。** 因此拼错的过滤条件的表现和没有过滤条件完全一样：
你会得到 `200` 和未过滤的结果，并为此付费。每个被忽略的字段都会列在 `x-warning` 响应头中（在 MCP
结果中则是 `warnings` 字段），所以当某个过滤条件看起来没有生效时，检查一下这个响应头。

在每个可能返回多条结果的端点上，`count` 都是必填的，而且它是上限而不是承诺：请求的数量超过实际
存在的数量时，会返回 `200` 和更少的结果行。

## 分页

REST API 上没有游标可以跟随。每次调用时把 `offset` 增加 `count`（或者对接受 `page` 参数的端点，
递增 `page`）。不推进就重复同样的调用会返回相同的结果 —— 这通常就是「API 一直给我重复数据」的原因。

## 重试

- `429`：等待 `retry_after` 秒（同样的值也在 `X-Retry-After` 中）后再重试。
- 来自端点的 `529`：该数据源的抓取能力正忙。请稍后重试，并把调用分散开，而不是并行发起。
- `500`：数据源不可达，或已放弃处理。数据源短暂不可用是正常现象；等待一段时间后重试，如果某个数据源
  持续故障而其他数据源正常，请从「支持」页面提交工单。
- 这两种情况在响应为空时免费，在仍然带回结果时计费 —— 请查看 `x-credits-charged`。
- `408`：本次调用会计费。重试会再次计费 —— 应缩小请求范围，而不是直接重试。
- 针对猜测出的标识符返回的 `412`：重试会得到相同的答案，并再次计费。应先用搜索端点找到该实体。
- 检查阶段返回的 `401`、`403`、`400`、`404`、`422`：重试同样的请求只会得到相同的错误。

Anysite 与数据源之间的传输失败已经替你自动重试过了 —— 最多重试三次，退避间隔分别为 1 秒、5 秒
和 10 秒 —— 你看到错误之前这些重试已经发生。这里没有幂等键：如果状态属于计费状态，你自己发起的重试
会再次计费。

## 端点可能被关闭

某个端点即使已经被关闭，仍可能出现在 [API 参考](/docs/api)中。列出的端点返回 `404`
`Endpoint was removed` 或 `529`，就是这种情况，而不是你这边的问题。如果你依赖它，请从「支持」页面
提交工单。

## Rate limits

套餐以及部分端点会在滚动窗口（10 秒、1 分钟、1 小时、1 天或 30 天）内限制请求数量。任何通过了之前
检查的请求都会计入这些限制，无论最终是否计费 —— 包括免费的 `209`。达到限制后，API 会返回 `429`，
`retry_after` 等于窗口中最早那次请求过期所需的秒数。

## Usage windows

部分套餐（包括 MCP 套餐）还会在两个滚动窗口内限制消耗的积分：**5 小时**和**每周**。部分套餐还会
加上单独的 LinkedIn 5 小时和每周窗口，只统计 LinkedIn 端点。用量是窗口内被计费积分的总和，所以随着
较早的调用移出窗口，额度会逐渐释放。

窗口用满后，API 会返回 `429`，`retry_after` 等于整个窗口的长度 —— 这是最长的等待时间，实际释放
可能更早。

对于 MCP 套餐，每个窗口当前的用量百分比会显示在「MCP 集成」页面上。

> ACTION FOR THE HUMAN: open https://app.anysite.io/mcp (查看 MCP 用量).
