Metadata-Version: 2.4
Name: everyinfra
Version: 0.3.0
Summary: EveryInfra official client — data, captcha, search and text models behind one key
Author: EveryInfra
License-Expression: MIT
Project-URL: Homepage, https://everyinfra.com
Project-URL: Documentation, https://everyinfra.com/docs
Keywords: captcha,scraping,search,api,everyinfra
Classifier: Programming Language :: Python :: 3
Classifier: Intended Audience :: Developers
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# everyinfra — EveryInfra 官方 Python 客户端

> v0.3.0 起在 PyPI 发布（包名 `everyinfra`）；同一份 wheel 也附在 EveryInfra 官方 GitHub Release，作为备选安装地址。服务是否开放及领取条件以官网和实际接口为准。

一把 Key、一个钱包，接入数据采集、验证码和搜索；充值过的账户还可以免费使用 OpenAI 兼容的 AI 接口。

```bash
pip install everyinfra
```

装不上 PyPI 时，可以直接装 GitHub Release 里的同一份 wheel（附 `SHA256SUMS`）：

```bash
pip install https://github.com/everyinfra/everyinfra-agent-plugins/releases/download/sdk-v0.3.0/everyinfra-0.3.0-py3-none-any.whl
```

```python
from everyinfra import EveryInfra

ei = EveryInfra("YOUR_API_KEY")

posts = ei.data("xiaohongshu", "search", keyword="露营", limit=20)
sol   = ei.captcha("turnstile", website_url="https://…", website_key="0x…")
hits  = ei.search("web", q="EU AI Act 合规要求")
acct  = ei.account()        # 余额、本月折扣档、每分钟次数、近 24 小时用量（只读、不计费）
```

零依赖（只用标准库）。完整 API 文档：<https://everyinfra.com/docs>

---

## 命令行

同一个包自带命令行 `everyinfra`（也可以 `python -m everyinfra`），给终端里的人和编码 agent 用：

```bash
pipx install everyinfra          # 或 uv tool install everyinfra；pip install everyinfra 也带命令行
everyinfra login                 # 先验 Key 再存到 ~/.config/everyinfra/config.json（权限 0600）
everyinfra catalog --find "tiktok comments"           # 免费：找能力
everyinfra describe tiktok/comments                   # 免费：参数、返回字段、价格
everyinfra data tiktok comments -p url=https://www.tiktok.com/@a/video/1 --limit 50 --dry-run
everyinfra data tiktok comments -p url=https://www.tiktok.com/@a/video/1 --limit 50 \
  --max-cost-usd 0.01 --output comments.json
```

- Key 依次读 `--key`、环境变量 `EVERYINFRA_API_KEY`、`everyinfra login` 存的那份。目录、`describe`、`--dry-run` 不要 Key。
- `-p key=value` 按目录里的参数类型转（整数、布尔、数组…），`-p key:=JSON` 原样传 JSON；数组参数可以写多次 `-p urls=A -p urls=B`。
- `--dry-run` 只打印请求和按标价估的费用，什么都不发；`--max-cost-usd` 估价超过就不发。
- 接管道或加 `--json` 时输出 JSON，`--jsonl` 每行一条，`--fields a,b` 只取字段，`--output FILE` 写全量、屏幕只给摘要。
  付费命令结束时 stderr 打一行本次扣费和余额；出错时 stderr 是和 API 同形的 JSON。
- 退出码：0 成功，2 请求不对，3 鉴权或权限，4 余额不足，5 限速（JSON 里带 `retry_after`），6 临时失败可重试，
  7 结果未知（不要重试，先用 `everyinfra requests --id …` 或 `everyinfra job …` 核对），1 其他。
- 其他命令：`search`、`captcha types` / `captcha solve`、`job`、`ai chat` / `ai map`（逐行跑 AI，遇限速自动等）、
  `account`、`usage`、`requests`、`billing`、`whats-new`、`support draft` / `support open`、`doctor`。
- `everyinfra mcp-config --client claude|cursor|codex|vscode` 打印各家的 MCP 配置（默认引用环境变量，不把 Key 写进文件）；
  `everyinfra skills install` 把给编码 agent 的使用说明装进 Claude Code、Codex、Cursor、VS Code 的技能目录。

---

## 类型补全是这个包的主要价值

我们的 API 是「一个端点 + 一个判别字段」，包一层 HTTP 对你几乎没有增量 ——
你自己写 `requests.post` 一样能用。这个包补上自动生成的判别值与必填参数。

```python
from everyinfra.catalog import CaptchaType, DataCapability, CAPTCHA_REQUIRED

CAPTCHA_REQUIRED["turnstile"]   # ('website_url', 'website_key')
CAPTCHA_REQUIRED["anubis"]      # ('website_url', 'proxy')
```

`Platform` / `DataCapability` / `CaptchaType` / `SearchTool`
都是 `Literal` 类型，IDE 直接补全、拼错当场标红。这份静态目录由后端注册表生成；
实时可用性仍以在线目录为准。

⚠ **目录里刻意没有价格和可用性。** 价格会调、`available` 每分钟都可能变化 ——
烤进一个版本化的包，你就会拿着旧数据算错账，或者调一个此刻根本不可用的能力。
那些请现读：`ei.captcha_types()` / `ei.search_tools()` / `ei.data_catalog()`（都免费、不鉴权）。

---

## 三件会影响你写法的事

### 1. 默认超时 300 秒，别随手调小

公网同步调用 **85 秒**内必回（超时回 `503 timeout`、不计费），验证码最长 **120 秒**。
客户端超时低于服务端还在算的时间会出现最坏的一种情况：**我们已经扣了费并且还在算，你先放弃了** ——
钱花了、结果没拿到，只能人工对账才发现。

真的不想等就用异步（对任何数据能力都生效，少数只能同步的批量能力会直接 422、不扣费）：

```python
sub = ei.data("xiaohongshu", "search", keyword="露营", mode="async")   # 立即返回 job_id
res = ei.wait_job(sub["job_id"])     # 轮询到结束；也可以自己隔一会儿 ei.job(job_id)
res["status"], res.get("results")    # succeeded / failed / submit_unknown
```

`data()` 每次调用都自动带 `Idempotency-Key`（同步、异步都是）：断线后 SDK 重发，同步调用拿回第一次的响应（`replayed: true`、billing 不变），异步拿回同一个任务，都不会再扣一次。要跨进程重试同一个逻辑请求，自己传一个稳定的 Key（`idempotency_key=`）。

### 2. 验证码的解有九种形状，我们不替你取值

| 形状 | 长什么样 | 怎么用 |
|---|---|---|
| `token` | `"0.aBcD…"` | 填回目标站表单 |
| `cookie` | `"name=值; Path=/"` | 作为 Cookie 带上后续请求 |
| `text` | `"3f8a"` | 填回输入框 |
| `number` | `42` | 按数值驱动你自己的浏览器 |
| `fields` | `{"ticket": …, "randstr": …}` | 多个字段一起回填 |
| `points` | `[{"x": 36, "y": 26}]` | 按坐标依次点击 |
| `boxes` | `[{"x_min": …, "x_max": …, …}]` | 按矩形拖框 |
| `tokens` | `["ct_aBc…", "ct_dEf…"]` | 一批令牌，在后续请求上逐个用掉 |
| `indices` | `[0, 2, 6]` | 按编号点格子（0 起，从左上角按行数） |

前四种是标量，响应里额外给一个顶层 `token` 可以直接用；**后五种没有**，
只能从 `solution` 取。调 `ei.captcha_types()` 看每种类型的 `solution.shape`。

⚠ 有几类**要你自带代理**（`CAPTCHA_REQUIRED` 里含 `proxy` 的那些）：它们交付的是
Cookie 而不是 token，而 Cookie 绑定求解时的出口 IP —— 用我们的出口解出来，
你拿去用是无效的。代理必须是**你后续请求要用的那个出口**。

### 3. 响应整包返回，`billing` 不要忽略

```python
r = ei.data("amazon", "reviews", urls=[...])
r["billing"]   # {"charged": true, "charged_usd": 0.111111, "balance_after_usd": 12.23, "units": 20, ...}
r["partial"]   # 扇出能力只采到一部分时为 true
r["missed"]    # 没采到的那几个目标 —— 这些不收钱
```

`billing.charged_usd` 是本次实扣（USD；没扣为 0，结果未知为 `None`），`billing.balance_after_usd`
是本次结算后的钱包余额（读不到时为 `None`）。`credits` / `cny` 等旧字段仍在，只作兼容。
我们不替你加工这些字段，因为那是你对账的依据。

---

## 查余额与用量

```python
acct = ei.account()                       # GET /api/v1/account，同一把 Key
acct["balance"]["total_usd"]              # 付费 + 赠送合计（USD），扣费时先用赠送余额
acct["balance"]["bonus_expiring"]         # 还没到期的限时赠送额度
acct["discount"]                          # 本月充值折扣档与距下一档的差额；经服务商开通的账户为 None
acct["rate_limit"]["requests_per_minute"] # 账户内所有 Key 合计的每分钟次数上限（AI 接口另有分档）
acct["usage_24h"]                         # 近 24 小时调用数与实际花费，this_key 是当前这把 Key 的
```

只读、不计费、不计入每分钟次数；任何产品线范围的 Key 都能读。跑批量前先看余额够不够，
跑的过程中看每次响应的 `billing.balance_after_usd` 就行，不用反复调它。

---

## 错误处理

```python
from everyinfra import BadRequest, OutcomeUnknown, PermissionDenied, QuotaExhausted

try:
    ei.data("xiaohongshu", "serch")          # 拼错了
except BadRequest as e:
    print(e.message)        # 会列出该平台全部 action 并给 did-you-mean
    print(e.did_you_mean)   # ['search'] —— 结构化候选，自动修正用它，别解析英文句子
    print(e.param, e.allowed)
    print(e.request_id)     # 报障时把这个给我们
```

内置重试**只覆盖确定不会重复扣费的失败**：503 服务暂时出错 / 超时（不计费）、429（按 `Retry-After`
退避）、请求根本没发出去的网络错误（连不上、DNS 失败）。**402 余额不足与 4xx 参数错一律不重试** ——
那些重试必然同样失败，只会让你以为程序还在正常工作。

| 异常 | 什么时候 | 怎么办 |
|---|---|---|
| `AuthError` | 401：没带 Key、Key 无效或过期 | 换 Key |
| `QuotaExhausted` | 402：余额不足 | 充值，不要等 |
| `AccountDisabled` | 403 `account_disabled` | 联系客服 |
| `PermissionDenied` | 其余 403：AI 接口未充值 / 已暂停、Key 的产品线或 IP 限制 | 看 `code` |
| `BadRequest` | 400 / 422：参数不合约 | 按 `did_you_mean` / `allowed` 改 |
| `RateLimited` | 429 | SDK 已按 `retry_after` 退避重试过 |
| `UpstreamError` | 503：暂时出错 / 超时，不计费 | SDK 已重试过；`timeout` 就改用 `mode="async"` |
| `OutcomeUnknown` | 结果未知：退款或提交在核对，或 POST 发出后断线 | **不要重发**，留着 `request_id` / `job_id` 先核对 |

`request_id` 一定要留着，那是我们排查一次具体调用的唯一凭据。

---

## AI 接口（充值用户免费）

`ei.chat()` 调 OpenAI 兼容的 AI 接口 `POST /api/v1/chat/completions`：充值过的账户免费用，不扣余额；输入不限内容和格式，用来整理和清洗你在 EveryInfra 采集的数据。按累计充值分档限速，账户内所有 Key 合计：不满 $100 每分钟 50 次，满 $100 每分钟 500 次，满 $500 每分钟 5000 次。同一账户同时在跑的请求数也按档位放宽（5 / 50 / 500 个）。分档是上限，不是承诺的吞吐：服务忙时返回 503 `ai_busy`。超限、并发满和服务忙都带 `Retry-After`。没充值过的账户返回 403 `ai_topup_required`。模型清单以 `GET /api/v1/models` 为准，默认 `gemini-3.6-flash`。

SDK 里的 `chat()` 只走非流式。要流式（`stream=True`），或者想用现成的 OpenAI 生态，直接用 OpenAI SDK，把 `base_url` 指到 `https://api.everyinfra.com/api/v1`，`api_key` 填同一把 EveryInfra Key。

数据清洗（`cleanup_*`）已在 2026-10-05 下线：原接口返回 410 `data_cleanup_retired`，改用上面的 AI 接口。

---

## 账户 Webhook：校验签名

在控制台「设置 → Webhook」填一个 HTTPS 地址，选「余额低于阈值」（`balance.low`）和/或「异步任务结束」（`job.finished`），余额降到阈值或任务结束时会收到签名的 POST。请求体是 `{"id", "type", "created_at", "data"}`；`job.finished` 不带结果数据，用提交任务的那把 Key 调 `GET /api/v1/jobs/{job_id}` 取。

```python
from everyinfra.webhooks import verify_signature

ok = verify_signature(signing_secret, request.headers, raw_body)   # 一定要传原始请求体
```

时间戳与当前时间相差超过 5 分钟会被拒绝（防重放）。投递是至少一次：返回任意 2xx 表示收到，否则按 30 秒、2 分钟、10 分钟、30 分钟、2 小时、6 小时重试，最多 7 次，所以请按 `id`（同 `X-EveryInfra-Event-Id` 头）去重。
