> 来源页面:https://iwkey.com/docs/(本页的 Markdown 版本) 文档 # API 接入文档 iwkey 统一网关的接入指南:可用模型、快速开始、调用入口、代码示例、SDK 配置与常见问题。 **文本调用 · cURL** ```bash curl https://iwkey.com/v1/chat/completions \ -H "Authorization: Bearer $IWKEY_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-opus-5-5", "messages": [ {"role": "user", "content": "Hello"} ] }' ``` **图像生成 · cURL** ```bash curl https://iwkey.com/v1/images/generations \ -H "Authorization: Bearer $IWKEY_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-image-2.5-sunburst", "prompt": "A minimalist studio product photo" }' ``` **视频模型任务 · cURL** ```bash curl https://iwkey.com/videos/v1/videos/generations \ -H "Authorization: Bearer $IWKEY_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "doubao-seedance-2.0", "prompt": "A cinematic product shot", "duration": 5, "resolution": "480p", "aspect_ratio": "16:9" }' ``` ## 01 文本、图像与视频模型,一个 Key 接入 统一查看 Claude、ChatGPT、图像生成与视频模型的调用入口、模型 ID 获取方式和状态;完整目录见 [模型页面](https://iwkey.com/models)。 ### 实时模型目录 模型 ID 以 `GET /v1/models` 返回为准;实时价格与分组见模型市场。 - [打开模型市场](https://iwkey.com/models) - [查看调用入口](https://iwkey.com/docs/#surfaces) - [**GET /v1/models** 查询当前可调用模型 ID](https://iwkey.com/docs/#surfaces) - [**POST /v1/chat/completions** OpenAI 兼容文本入口](https://iwkey.com/docs/#surfaces) - [**POST /v1/messages** Anthropic Messages 入口](https://iwkey.com/docs/#surfaces) - [**POST /v1/images/generations** OpenAI 兼容图像生成入口](https://iwkey.com/docs/#surfaces) - [**POST /videos/v1/videos/generations** 视频异步任务入口](https://iwkey.com/docs/#surfaces) - [**GET /videos/v1/videos/jobs/{job_id}** 使用提交响应返回的 poll_url 查询任务结果](https://iwkey.com/docs/#surfaces) 接口定义见 [`/docs/openapi.json`](https://iwkey.com/docs/openapi.json);模型详情以实时 `/v1/models` 与 [模型市场](https://iwkey.com/models/) 为准。 **curl · List models** ```bash curl https://iwkey.com/v1/models \ -H "Authorization: Bearer $IWKEY_KEY" ``` ## 02 三步接入 STEP 01 #### 申请 API Key [登录控制台](https://iwkey.com/login?lng=zh),创建 API Key。 STEP 02 #### 选择调用入口 文本模型替换 SDK base URL;视频模型用 `/videos/v1/videos/generations` 提交任务。 STEP 03 #### 查看结果与用量 文本请求实时返回;视频模型按任务轮询,完成后可下载结果,并进入使用记录汇总。 ## 03 调用入口 GET #### `/v1/models` 接入前查询当前可调用模型 ID。ChatGPT 模型 ID 以此列表为准。 POST #### `/v1/chat/completions` OpenAI 兼容入口。完成 base URL 替换后,OpenAI SDK、Cursor、Codex CLI、opencode、LangChain 可走此入口。 POST #### `/v1/messages` Anthropic Messages 原生入口。Claude Code 与 Anthropic SDK 可直接切换到 iwkey base URL。 POST #### `/v1/images/generations` OpenAI 兼容图像生成入口。支持按张计费生图模型(如 `gpt-image-2.5-sunburst`),单价见模型市场。Nano Banana 2(`gemini-3.1-flash-image`)不走此入口,请改用 Gemini 原生 `/v1beta/models/gemini-3.1-flash-image:generateContent` 或 `/v1/chat/completions`(图片随回复内容返回)。 POST #### `/videos/v1/videos/generations` 视频模型异步任务入口。提交 prompt、时长、清晰度和画幅后返回 job_id 与 poll_url。 GET #### `/videos/v1/videos/jobs/{job_id}` 优先请求提交响应返回的 `poll_url`;该地址用于查询任务状态、完成结果、失败原因和视频下载地址。 POST #### `/videos/api/v3/contents/generations/tasks` 火山方舟原生格式的视频模型入口。`model` 可直接填官方 ID(`doubao-seedance-2-0-260128` / `doubao-seedance-2-0-fast-260128` / `doubao-seedance-2-5-260628`);顶层不支持的参数会明确报错,不会静默丢弃。 GET #### `/videos/api/v3/contents/generations/tasks/{id}` 方舟原生格式的任务查询入口,返回方舟状态枚举(`queued` / `running` / `succeeded` / `failed` / `cancelled` / `expired`)。成功时 `content.video_url` 为生成引擎官方直链(有效期通常约 24 小时),与标准接口的 `video_url` 完全一致;若该任务有平台副本,会额外附带 `content.platform_video_url` 与 `content.platform_expires_at`(本平台保存 7 天;这两个字段为本平台额外返回,方舟原生格式中没有)。 已经在用火山方舟原生请求格式的开发者,可以直接使用上面的方舟兼容入口;与方舟原生的具体差异见视频模型 API 接入指南「[方舟兼容入口:与方舟原生的差异](https://iwkey.com/docs/video-model/#ark-diff)」。 ## 04 进阶场景示例 文本高级能力与视频模型异步任务示例,每个场景给出 cURL / Python / JavaScript 三种写法。 - [Anthropic 文本协议 ↗](https://docs.anthropic.com/en/api/messages) - [OpenAI 文本协议 ↗](https://platform.openai.com/docs/api-reference/chat) - [视频工作台](https://iwkey.com/videos/create.html) **cURL · 流式响应(SSE)** ```bash curl -N https://iwkey.com/v1/chat/completions \ -H "Authorization: Bearer $IWKEY_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-opus-5-5", "messages": [{"role": "user", "content": "写一首关于云的短诗"}], "stream": true }' ``` **Python · 流式响应** ```python from openai import OpenAI client = OpenAI(base_url="https://iwkey.com/v1", api_key="$IWKEY_KEY") stream = client.chat.completions.create( model="claude-opus-5-5", messages=[{"role": "user", "content": "写一首关于云的短诗"}], stream=True, ) for chunk in stream: print(chunk.choices[0].delta.content or "", end="") ``` **JavaScript · 流式响应** ```javascript import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://iwkey.com/v1", apiKey: process.env.IWKEY_KEY, }); const stream = await client.chat.completions.create({ model: "claude-sonnet-5-5", messages: [{ role: "user", content: "写一首关于云的短诗" }], stream: true, }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0].delta.content ?? ""); } ``` **cURL · 视频模型异步任务** ```bash # 1. Submit job and capture the returned poll_url POLL_URL=$(curl -sS https://iwkey.com/videos/v1/videos/generations \ -H "Authorization: Bearer $IWKEY_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "doubao-seedance-2.0", "prompt": "A cinematic product shot on a clean studio desk", "duration": 5, "resolution": "480p", "aspect_ratio": "16:9" }' | jq -r '.poll_url') # 2. Poll the exact URL returned by the API curl "$POLL_URL" \ -H "Authorization: Bearer $IWKEY_KEY" ``` **cURL · 火山方舟原生格式** ```bash # 1. 提交任务(方舟原生结构:content 数组 + ratio 字段) TASK_ID=$(curl -sS https://iwkey.com/videos/api/v3/contents/generations/tasks \ -H "Authorization: Bearer $IWKEY_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "doubao-seedance-2-0-260128", "content": [ { "type": "text", "text": "A cinematic product shot on a clean studio desk" } ], "duration": 5, "resolution": "480p", "ratio": "16:9" }' | jq -r '.id') # 2. 查询任务(成功时 content.video_url = 官方直链约 24h; # 有平台副本时另有 content.platform_video_url,本平台保存 7 天) curl "https://iwkey.com/videos/api/v3/contents/generations/tasks/$TASK_ID" \ -H "Authorization: Bearer $IWKEY_KEY" ``` **Python · 视频模型异步任务** ```python import os import time import requests headers = { "Authorization": f"Bearer {os.environ['IWKEY_KEY']}", "Content-Type": "application/json", } job = requests.post( "https://iwkey.com/videos/v1/videos/generations", headers=headers, json={ "model": "doubao-seedance-2.0", "prompt": "A cinematic product shot on a clean studio desk", "duration": 5, "resolution": "480p", "aspect_ratio": "16:9", }, ) job.raise_for_status() poll_url = job.json()["poll_url"] while True: result = requests.get(poll_url, headers=headers) result.raise_for_status() data = result.json() if data["status"] in {"completed", "failed", "cancelled", "timeout"}: print(data) break time.sleep(5) ``` **JavaScript · 视频模型异步任务** ```javascript const headers = { Authorization: `Bearer ${process.env.IWKEY_KEY}`, "Content-Type": "application/json", }; const submit = await fetch("https://iwkey.com/videos/v1/videos/generations", { method: "POST", headers, body: JSON.stringify({ model: "doubao-seedance-2.0", prompt: "A cinematic product shot on a clean studio desk", duration: 5, resolution: "480p", aspect_ratio: "16:9", }), }); const job = await submit.json(); while (true) { const res = await fetch(job.poll_url, { headers }); const result = await res.json(); if (["completed", "failed", "cancelled", "timeout"].includes(result.status)) { console.log(result); break; } await new Promise((resolve) => setTimeout(resolve, 5000)); } ``` 视频任务参数参考 | 参数 | 类型 | 说明 | | --- | --- | --- | | `model` | string | `doubao-seedance-2.0` / `doubao-seedance-2.0-fast` / `doubao-seedance-2.5` / `MiniMax-H3` | | `prompt` | string | 文本描述,必填 | | `duration` | integer | 时长(秒),`doubao-seedance-2.0` / `doubao-seedance-2.0-fast` 与 `MiniMax-H3` 为 4–15,`doubao-seedance-2.5` 为 4–30 | | `resolution` | string | 按模型取值:`doubao-seedance-2.0` 为 `480p` / `720p` / `1080p` / `4k`;`doubao-seedance-2.0-fast` 为 `480p` / `720p`;`doubao-seedance-2.5` 为 `480p` / `720p` / `1080p`;`MiniMax-H3` 为 `768p` / `2k` | | `image_urls` | array ≤9(`doubao-seedance-2.5` ≤30) | 参考图片。每项可以是 URL 字符串(默认角色 = `first_frame`,图生视频)或对象 `{"url":"…","role":"first_frame\|last_frame\|reference_image"}` | | `video_urls` | array ≤3(`doubao-seedance-2.5` ≤10) | 参考视频 | | `audio_urls` | array ≤3(`doubao-seedance-2.5` ≤10) | 参考音频;`doubao-seedance-2.5` 可单独使用;`doubao-seedance-2.0`、`doubao-seedance-2.0-fast`、`MiniMax-H3` **不能单独使用**,必须搭配 `role:"reference_image"` 的图片或 `video_urls`;首帧/尾帧图片不能与音频同时使用 | | `generate_audio` | boolean | 输出是否带音频,默认 `false` | **cURL · 含参考图与音频** ```bash curl https://iwkey.com/videos/v1/videos/generations \ -H "Authorization: Bearer $IWKEY_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "doubao-seedance-2.0", "prompt": "产品展示视频,柔和灯光,镜头缓慢推进", "duration": 5, "resolution": "720p", "image_urls": [ {"url": "https://example.com/product.jpg", "role": "reference_image"} ], "audio_urls": ["https://example.com/bgm.mp3"], "generate_audio": true }' ``` **计费:**提交时预扣,成功后按实际用量结算,多退少补;失败全额退款。 出片链接说明 | 字段 | 说明 | | --- | --- | | `video_url` | 生成引擎官方原始出片链接,**约 24 小时**有效(有效期以 `expires_at` 为准)。 | | `platform_video_url` | 平台存储链接,自生成完成起**保留 7 天**,到期后文件删除且不可恢复。未启用平台存储或无副本时为 `null`,此时用 `video_url`。 | | `platform_expires_at` | `platform_video_url` 的到期时间。 | **cURL · 工具调用(Function Calling)** ```bash curl https://iwkey.com/v1/chat/completions \ -H "Authorization: Bearer $IWKEY_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-opus-5-5", "messages": [{"role": "user", "content": "杭州天气怎么样?"}], "tools": [{ "type": "function", "function": { "name": "get_weather", "parameters": { "type": "object", "properties": {"city": {"type": "string"}} } } }] }' ``` **Python · 工具调用** ```python from openai import OpenAI client = OpenAI(base_url="https://iwkey.com/v1", api_key="$IWKEY_KEY") resp = client.chat.completions.create( model="claude-opus-5-5", messages=[{"role": "user", "content": "杭州天气怎么样?"}], tools=[{ "type": "function", "function": { "name": "get_weather", "parameters": { "type": "object", "properties": {"city": {"type": "string"}}, }, }, }], ) print(resp.choices[0].message.tool_calls) ``` **JavaScript · 工具调用** ```javascript import OpenAI from "openai"; const client = new OpenAI({ baseURL: "https://iwkey.com/v1", apiKey: process.env.IWKEY_KEY, }); const resp = await client.chat.completions.create({ model: "claude-sonnet-5-5", messages: [{ role: "user", content: "杭州天气怎么样?" }], tools: [{ type: "function", function: { name: "get_weather", parameters: { type: "object", properties: { city: { type: "string" } }, }, }, }], }); console.log(resp.choices[0].message.tool_calls); ``` 平台采用官方标准模型 ID,推理强度通过 **API 参数** 控制——Anthropic 用 `thinking` 对象,OpenAI 用 `reasoning_effort`。 **Anthropic Claude · 启用 Extended Thinking** ``` curl https://iwkey.com/v1/messages \ -H "Authorization: Bearer $IWKEY_KEY" \ -H "Content-Type: application/json" \ -H "anthropic-version: 2023-06-01" \ -d '{ "model": "claude-opus-5-5", "max_tokens": 16000, "thinking": { "type": "adaptive", "display": "summarized" }, "output_config": { "effort": "high" }, "messages": [{ "role": "user", "content": "解释量子纠缠的机制" }] }' # Claude Opus 5.5 / Sonnet 5 / Fable 5.1:thinking 用 "adaptive",推理深度由 output_config.effort 控制(low / medium / high / xhigh / max) ``` **Python · Anthropic SDK** ```python import anthropic client = anthropic.Anthropic( base_url="https://iwkey.com", api_key="$IWKEY_KEY", ) resp = client.messages.create( model="claude-opus-5-5", max_tokens=16000, thinking={"type": "adaptive", "display": "summarized"}, output_config={"effort": "high"}, messages=[{"role": "user", "content": "解释量子纠缠的机制"}], ) for block in resp.content: if block.type == "thinking": print("[思考过程]", block.thinking[:200], "...") elif block.type == "text": print("[回答]", block.text) ``` **OpenAI · reasoning_effort 参数** ``` curl https://iwkey.com/v1/chat/completions \ -H "Authorization: Bearer $IWKEY_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-6-sol", "reasoning_effort": "high", "messages": [{ "role": "user", "content": "解释量子纠缠的机制" }] }' # reasoning_effort: "low" | "medium" | "high"(默认 medium) # 适用于 gpt-6-sol 等支持推理的模型;查 /v1/models 确认当前可用 ID ``` **Python · OpenAI SDK** ```python from openai import OpenAI client = OpenAI(base_url="https://iwkey.com/v1", api_key="$IWKEY_KEY") resp = client.chat.completions.create( model="gpt-6-sol", reasoning_effort="high", messages=[{"role": "user", "content": "解释量子纠缠的机制"}], ) print(resp.choices[0].message.content) ``` ## 05 各客户端配置示例 #### Claude Code Claude 全系 ⚠ `ANTHROPIC_BASE_URL` 只到主机名 `https://iwkey.com`,不要加 `/v1`(与 OpenAI 接口不同;加了会拼成 /v1/v1/messages → 404) ``` # 设置环境变量后直接启动 export ANTHROPIC_BASE_URL=https://iwkey.com export ANTHROPIC_API_KEY=your-iwkey-key claude ``` #### Cursor Claude + ChatGPT ⚠ Base URL 须带 `/v1`(OpenAI 协议入口,Cursor 直接拼接 `/chat/completions`;与 Claude Code 的 `ANTHROPIC_BASE_URL` 不带 /v1 不同) ``` # Settings → Models → Override OpenAI Base URL Base URL: https://iwkey.com/v1 API Key: your-iwkey-key # Claude 可直接填模型 ID(如 claude-sonnet-5-5);ChatGPT 模型 ID 先查 /v1/models ``` #### Anthropic Python SDK Claude 全系 ``` import anthropic client = anthropic.Anthropic( base_url="https://iwkey.com", api_key="your-iwkey-key", ) msg = client.messages.create( model="claude-opus-5-5", max_tokens=1024, messages=[{"role": "user", "content": "Hello"}], ) ``` #### OpenAI Python SDK Claude + ChatGPT ``` # model 换名即切 provider from openai import OpenAI client = OpenAI( base_url="https://iwkey.com/v1", api_key="your-iwkey-key", ) # Claude resp = client.chat.completions.create( model="claude-opus-5-5", messages=[{"role": "user", "content": "Hello"}], ) # ChatGPT — 同一个 client,具体模型 ID 先查 /v1/models resp = client.chat.completions.create( model="gpt-6-sol", messages=[{"role": "user", "content": "Hello"}], ) ``` #### 视频模型 API Seedance / 视频任务 ``` # 标准 HTTP 调用:提交任务后轮询 poll_url curl https://iwkey.com/videos/v1/videos/generations \ -H "Authorization: Bearer your-iwkey-key" \ -H "Content-Type: application/json" \ -d '{"model":"doubao-seedance-2.0","prompt":"A product video","duration":5,"resolution":"480p","aspect_ratio":"16:9"}' ``` #### Codex CLI ChatGPT 系(Responses API) ⚠ `base_url` 只到 `/v1`,不要加 `/chat/completions`,否则 Codex 会拼出错误路径导致 404 ⚠ 模型用 `gpt-6-sol`,Codex 走 Responses API(非 chat/completions),需在 config.toml 声明 `wire_api = "responses"` ``` # ~/.codex/config.toml model = "gpt-6-sol" model_provider = "iwkey" [model_providers.iwkey] name = "iwkey" base_url = "https://iwkey.com/v1" wire_api = "responses" env_key = "IWKEY_KEY" ``` ``` export IWKEY_KEY="sk-你的令牌" codex ``` #### opencode Claude + ChatGPT ⚠ `options.baseURL` 须带 `/v1`(`@ai-sdk/openai-compatible` 直接拼接 `/chat/completions`,不会自动补 /v1) ``` # ~/.config/opencode/opencode.json { "$schema": "https://opencode.ai/config.json", "provider": { "iwkey": { "npm": "@ai-sdk/openai-compatible", "name": "iwkey", "options": { "baseURL": "https://iwkey.com/v1", "apiKey": "{env:IWKEY_KEY}" }, "models": { "claude-sonnet-5-5": { "name": "Claude Sonnet 5" }, "gpt-6-sol": { "name": "GPT-6 Sol" } } } } } ``` ``` # 设置环境变量后运行 /connect 存凭据,provider id 填 "iwkey" export IWKEY_KEY="your-iwkey-key" opencode # TUI 内:/connect → Other → 粘贴 Key,provider id 填 "iwkey" # 完成后 /models 选择上面配置的模型 ``` #### LangChain Claude 全系 ``` from langchain_anthropic import ChatAnthropic llm = ChatAnthropic( base_url="https://iwkey.com", api_key="your-iwkey-key", model="claude-opus-5-5", ) ``` ## 06 视频模型 视频生成是异步任务,支持参考素材与素材库,接入规范见**独立文档**:[/docs/video-model/](https://iwkey.com/docs/video-model/)(共用 API Key 与入口域名)。 1. [1 三分钟跑通第一条视频](https://iwkey.com/docs/video-model/#intro) 2. [2 参考图](https://iwkey.com/docs/video-model/#ref-images) 3. [3 素材库:真人形象的唯一通道](https://iwkey.com/docs/video-model/#asset-library) 4. [4 人物命名与位置语法](https://iwkey.com/docs/video-model/#mention-syntax) 5. [5 接口参考](https://iwkey.com/docs/video-model/#api-reference) 1. [5.1 提交生成任务 · `POST /videos/v1/videos/generations`](https://iwkey.com/docs/video-model/#api-submit) 2. [5.2 提交前估价 · `POST /videos/v1/videos/generations/estimate`](https://iwkey.com/docs/video-model/#api-estimate) 3. [5.3 查询任务 · `GET /videos/v1/videos/jobs/{job_id}`](https://iwkey.com/docs/video-model/#api-get-job) 4. [5.4 任务列表 · `GET /videos/v1/videos/jobs`](https://iwkey.com/docs/video-model/#api-list-jobs) 5. [5.5 消费统计 · `GET /videos/v1/videos/jobs/stats`](https://iwkey.com/docs/video-model/#api-stats) 6. [5.6 导出 · `GET /videos/v1/videos/jobs/export.csv`](https://iwkey.com/docs/video-model/#api-export) 7. [5.7 素材库](https://iwkey.com/docs/video-model/#api-assets) 8. [5.8 上传接口(视频工作台专用,API 接入通常不需要)](https://iwkey.com/docs/video-model/#api-uploads) 9. [5.9 方舟兼容入口:与方舟原生的差异](https://iwkey.com/docs/video-model/#ark-diff) 6. [6 错误码](https://iwkey.com/docs/video-model/#error-codes) 1. [6.1 业务与参数类错误码](https://iwkey.com/docs/video-model/#error-codes-request) 2. [6.2 鉴权与服务端错误码](https://iwkey.com/docs/video-model/#error-codes-auth-server) 3. [6.3 重试建议](https://iwkey.com/docs/video-model/#error-codes-retry) 7. [7 归属与隔离边界](https://iwkey.com/docs/video-model/#tenancy) 8. [8 配额与限制](https://iwkey.com/docs/video-model/#quotas) 9. [9 常见接入问题](https://iwkey.com/docs/video-model/#faq) ## 07 账户余额查询 在发起调用前、或在你自己的调度服务里查一下余额:余额不足时提前预警,避免请求中途因余额不足被拒。只读,查询不收费。 `GET https://iwkey.com/v1/usage` `api.infwave.com` 同样可用。鉴权用你调用模型的同一把 API Key:`Authorization: Bearer <你的密钥>`(也接受 `x-api-key` 请求头)。 **curl 示例 · 查询余额** ```bash curl https://iwkey.com/v1/usage \ -H "Authorization: Bearer <你的 API Key>" ``` **响应示例(普通 Key,数值为演示,仅列常用字段)** ``` { "isValid": true, "mode": "unrestricted", "planName": "钱包余额", "balance": 61.66, "remaining": 61.66, "unit": "USD", "usage": { "today": { "requests": 12, "total_tokens": 48210, "actual_cost": 0.42 }, "total": { "requests": 1893, "total_tokens": 9120000, "actual_cost": 38.34 }, "rpm": 0, "tpm": 0 } } ``` **返回什么取决于这把 Key**:同一个接口,下面三种 Key 返回的含义不同,接入前先确认你用的是哪一种。 | Key 类型 | 返回内容 | | --- | --- | | 普通 Key | **账户的钱包余额**:`planName` 为「钱包余额」,`balance` / `remaining` 是余额,`unit` 为 `USD` | | 设了额度上限或速率限制的 Key | **这把 Key 自身的剩余额度**,不是账户余额 | | 订阅套餐分组的 Key | 套餐剩余额度,以及日 / 周 / 月已用量、对应上限和到期时间 | 三种情况都会附带 `usage`:今日与累计的请求数、token 数和费用,以及 `rpm`、`tpm`。 **查询频率**:余额变化不快,建议每小时查询一次即可。 **用程序调用必须设置 User-Agent**:用 Python 等程序调用时,请求头里要带上 `User-Agent`,否则请求可能被 Cloudflare 拦截(返回 403,或直接连接失败);curl 不受影响。下面的示例脚本已经带上。 ### 余额提醒 余额偏低时提前收到通知,避免调用因余额不足中断。用定时任务每小时调用一次上面的接口,余额低于你设定的线时,把提醒发到你自己的飞书群、钉钉群、企业微信群、手机短信或 Telegram 等通道。示例只用 Python 标准库,通过环境变量 `NOTIFY_CHANNEL` 在飞书、钉钉、企业微信、Telegram 之间切换;短信需要接入你们自己的短信服务商,见下表。 | 平台 | 先准备 | 环境变量 | | --- | --- | --- | | 飞书(`feishu`) | 在群里添加「自定义机器人」,复制 webhook 地址;机器人如果开启了「签名校验」,同时复制密钥 | `FEISHU_WEBHOOK`;开了签名校验再加 `FEISHU_SECRET` | | 钉钉(`dingtalk`) | 在群里添加「自定义机器人」,复制 webhook 地址;安全设置如果选了「加签」,同时复制密钥 | `DINGTALK_WEBHOOK`;选了加签再加 `DINGTALK_SECRET` | | 企业微信(`wecom`) | 在群里添加「群机器人」,复制 webhook 地址 | `WECOM_WEBHOOK` | | 短信 | 在阿里云短信、腾讯云短信等服务商开通,先申请短信签名和模板,审核通过后才能发送。示例未内置短信,把示例里的发送函数换成服务商的发送接口即可 | 按服务商要求(如密钥、签名、模板 ID、接收手机号) | | Telegram(`telegram`) | 用 @BotFather 创建机器人拿到 token,把机器人加进目标群或私聊,取得 chat_id。国内服务器直连 Telegram 通常不通,需要能访问 Telegram 的网络 | `TG_BOT_TOKEN`、`TG_CHAT_ID` | 另需设置 `IWKEY_KEY`(你调用模型的 API Key);`LOW_USD` / `CRIT_USD` 为偏低线 / 告急线(美元),默认 40 / 10。webhook 地址和 bot token 等同于密码,不要外传。示例读取的是 `balance`:普通 Key 是账户钱包余额;设了额度上限的 Key 读到的是这把 Key 的剩余额度,提醒线请按此理解;订阅套餐分组的 Key 返回结构不同,本示例不适用。示例的行为:偏低、告急各提醒一次,同一档不重复;充值后回到偏低线的 1.2 倍以上才算恢复;查询失败不当作余额不足,连续 3 次失败会提醒「监控断了」;消息发送失败不记账,下一轮重试。建议用 crontab 每小时运行一次:`0 * * * *`。 **Python 示例 · 余额低于阈值时推送到群(飞书 / 钉钉 / 企业微信 / Telegram)** ```python #!/usr/bin/env python3 """iwkey 余额提醒:每小时查一次余额,低于你设的线就推送到飞书 / 钉钉 / 企业微信 / Telegram。只用 Python 标准库。 环境变量: IWKEY_KEY 调用模型用的 API Key NOTIFY_CHANNEL feishu / dingtalk / wecom / telegram,四选一 飞书:FEISHU_WEBHOOK(机器人开了签名校验再加 FEISHU_SECRET) 企业微信:WECOM_WEBHOOK 钉钉:DINGTALK_WEBHOOK(机器人开了加签再加 DINGTALK_SECRET) Telegram:TG_BOT_TOKEN、TG_CHAT_ID LOW_USD / CRIT_USD 偏低线 / 告急线(美元),默认 40 / 10 定时运行(crontab,每小时一次): 0 * * * * /usr/bin/python3 /path/to/balance_alert.py >> balance_alert.log 2>&1 行为:偏低、告急各提醒一次;查询失败不当作余额不足,连续 3 次失败提醒「监控断了」。""" import base64 import hashlib import hmac import json import os import time import urllib.parse import urllib.request from pathlib import Path API = "https://iwkey.com/v1/usage" KEY = os.environ.get("IWKEY_KEY", "") CHANNEL = os.environ.get("NOTIFY_CHANNEL", "feishu") # feishu / wecom / dingtalk / telegram LOW = float(os.environ.get("LOW_USD", "40")) CRIT = float(os.environ.get("CRIT_USD", "10")) STATE = Path(os.environ.get("STATE_FILE", str(Path.home() / ".iwkey_balance_alert.json"))) TIERS = ["正常", "偏低", "告急"] UA = "balance-alert/1.0" # 必须带 User-Agent,否则可能被 Cloudflare 拦截(403 / 连接失败) def http_json(url, payload=None, headers=None): data = json.dumps(payload).encode() if payload is not None else None h = {"User-Agent": UA, **(headers or {})} if data: h["Content-Type"] = "application/json" req = urllib.request.Request(url, data=data, headers=h) with urllib.request.urlopen(req, timeout=15) as r: return json.loads(r.read().decode()) def query_balance(): data = http_json(API, headers={"Authorization": f"Bearer {KEY}"}) usd = float(data.get("balance", data.get("remaining"))) return usd, f"${usd:.2f}" def send_once(text): if CHANNEL == "feishu": # 飞书自定义机器人 webhook payload = {"msg_type": "text", "content": {"text": text}} secret = os.environ.get("FEISHU_SECRET") if secret: # 机器人开了「签名校验」才需要 ts = str(int(time.time())) key = f"{ts}\n{secret}".encode() payload["timestamp"] = ts payload["sign"] = base64.b64encode(hmac.new(key, b"", hashlib.sha256).digest()).decode() return http_json(os.environ["FEISHU_WEBHOOK"], payload).get("code") == 0 if CHANNEL == "wecom": # 企业微信群机器人 webhook res = http_json(os.environ["WECOM_WEBHOOK"], {"msgtype": "text", "text": {"content": text}}) return res.get("errcode") == 0 if CHANNEL == "dingtalk": # 钉钉群自定义机器人 webhook url = os.environ["DINGTALK_WEBHOOK"] secret = os.environ.get("DINGTALK_SECRET") if secret: # 机器人开了「加签」才需要 ts = str(int(time.time() * 1000)) digest = hmac.new(secret.encode(), f"{ts}\n{secret}".encode(), hashlib.sha256).digest() url += f"×tamp={ts}&sign={urllib.parse.quote_plus(base64.b64encode(digest))}" res = http_json(url, {"msgtype": "text", "text": {"content": text}}) return res.get("errcode") == 0 if CHANNEL == "telegram": # Telegram bot(国内服务器通常连不上) url = f"https://api.telegram.org/bot{os.environ['TG_BOT_TOKEN']}/sendMessage" return http_json(url, {"chat_id": os.environ["TG_CHAT_ID"], "text": text}).get("ok") is True raise SystemExit("NOTIFY_CHANNEL 只能是 feishu / wecom / dingtalk / telegram") def notify(text): for i in range(3): try: if send_once(text): return True print("消息被拒收,稍后重试") except Exception as e: print(f"消息发送异常:{type(e).__name__}") # 只打印异常类型,不打印网址,避免 token 进日志 time.sleep(2 ** i) return False def load_state(): try: return json.loads(STATE.read_text()) except Exception: return {"tier": 0, "fails": 0, "down_alerted": False} def main(): if not KEY: raise SystemExit("请先设置 IWKEY_KEY") st = load_state() try: usd, shown = query_balance() except Exception as e: st["fails"] += 1 print(f"查询失败(第 {st['fails']} 次):{type(e).__name__}") if st["fails"] >= 3 and not st["down_alerted"]: if notify("【iwkey 余额监控】已连续 3 次查不到余额,监控可能中断,请检查网络或 API Key。"): st["down_alerted"] = True STATE.write_text(json.dumps(st)) return st["fails"], st["down_alerted"] = 0, False tier = 2 if usd < CRIT else 1 if usd < LOW else 0 if tier > st["tier"]: # 跌进更差的一档才提醒,同一档不重复提醒 line = CRIT if tier == 2 else LOW msg = (f"【iwkey 余额{TIERS[tier]}】当前可用余额 {shown}," f"已低于{TIERS[tier]}线 ${line:.0f},请及时充值,以免调用中断。") if notify(msg): st["tier"] = tier # 发送成功才记账,失败下轮重试 elif tier < st["tier"] and (tier > 0 or usd >= LOW * 1.2): # 回到偏低线的 1.2 倍以上才算恢复,避免在线附近反复提醒 st["tier"] = tier if tier == 0: notify(f"【iwkey 余额已恢复】当前可用余额 {shown}。") print(f"余额 ${usd:.2f},档位 {TIERS[st['tier']]}") STATE.write_text(json.dumps(st)) if __name__ == "__main__": main() ``` ## 08 常见问题 **返回格式和官方 API 一致吗?** 一致。响应体透传上游原生字段(Anthropic 的 `request_id`、OpenAI 的 `id` 与 `system_fingerprint`)。文本请求不留存提示词和回答正文,完整边界见 [透明度页面](https://iwkey.com/transparency)。 **计费方式是什么?** 文本模型按 token 用量计费;视频模型按模型与时长计费。登录控制台后可查看实时余额、用量明细,可按模型/日期/Key 筛选查看。支持对公转账 + 增值税发票。 **你们会存储我的 prompt 和 response 吗?** 文本请求与响应正文不存数据库。视频模型属于异步任务,任务参数、状态、成本、结果地址和失败原因会保留用于任务追踪与计费;完整边界以 [透明度页面](https://iwkey.com/transparency) 为准。 **支持 Streaming 吗?** 文本模型支持 Anthropic 原生 SSE streaming 和 OpenAI 兼容 streaming,在请求中设置 `"stream": true` 即可。视频模型不是 streaming 响应;提交任务后应优先请求响应中的 `poll_url`,当前地址为 `/videos/v1/videos/jobs/{job_id}`。 **视频模型怎么调用?** 使用同一个 API Key 调用 `POST /videos/v1/videos/generations`,提交后会返回 `job_id` 和 `poll_url`。任务完成后可在轮询响应或「视频模型使用记录」里查看结果、下载地址和失败原因。 **如何启用推理增强(Extended Thinking / reasoning_effort)?** 平台只提供官方标准模型 ID,不提供 `-thinking`/`-high`/`-low` 等后缀变体。推理强度由 API 参数控制: - **Anthropic Claude**:`claude-opus-5-5`、`claude-sonnet-5-5`、`claude-fable-5-1` 在请求体加 `"thinking": {"type": "adaptive"}`,用 `"output_config": {"effort": "high"}` 调推理深度(可选 `low`/`medium`/`high`/`xhigh`/`max`)。 - **OpenAI(推理模型)**:在请求体加 `"reasoning_effort": "high"`(可选 `low`/`medium`/`high`)。支持模型:`gpt-6-sol` 等,查 `GET /v1/models` 确认。 完整示例见[代码示例 → 推理增强(Thinking)](https://iwkey.com/docs/#examples) 标签。 **如何启用提示缓存(Prompt Caching)?** 💡 **提示缓存(Prompt Caching)**:Claude 模型支持提示缓存,对重复的长上下文(system prompt、Agent 循环、代码库上下文等)可节省最高约 90% 的输入费用。**启用方式**:使用 Anthropic 原生 `/v1/messages` 格式,并在消息/系统提示中标记 `cache_control` 断点。**注意**:OpenAI 兼容的 `/v1/chat/completions` 格式不支持 Claude 提示缓存,每次请求按全价输入计费。 **和直连 Anthropic / OpenAI API 有什么区别?** 文本接口与 Anthropic / OpenAI 官方格式兼容,支持 Streaming 和 Tool Calling;视频模型走 iwkey 的异步任务接口。同一个 Key、余额和使用记录打通,支持人民币对公结算与增值税发票。