API 对接文档

快速开始

TokenGet 完全兼容 OpenAI API 协议。只需将已有应用的 base_url 指向本站,并使用本站签发的 API Key 即可。

  • 1. 注册账号(用户名密码或微信扫码),在控制台创建 API Key(sk-tg-...)。
  • 2. Base URL:http://<服务器地址>/v1
  • 3. 通过 GET/v1/models?category=llm 按类别拉取模型列表;一个模型可以同时属于多个分类(如 ["llm","image"]),支持 ?category=llm&category=image 取交集。
curl http://localhost:8000/v1/chat/completions \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"你好"}]}'
import requests

# Python 用户也可以直接使用官方 openai SDK:
#   client = OpenAI(base_url="http://localhost:8000/v1", api_key="sk-tg-你的密钥")
r = requests.post(
    "http://localhost:8000/v1/chat/completions",
    headers={"Authorization": "Bearer sk-tg-你的密钥"},
    json={
        "model": "gpt-4o-mini",
        "messages": [{"role": "user", "content": "你好"}],
    },
    timeout=120,
)
data = r.json()
print(data["choices"][0]["message"]["content"])
print(data["usage"])  # prompt_tokens / completion_tokens / total_tokens
// JDK 11+(java.net.http),无第三方依赖
var http = java.net.http.HttpClient.newHttpClient();
var body = """
    {"model":"gpt-4o-mini",
     "messages":[{"role":"user","content":"你好"}]}
    """.strip();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/v1/chat/completions"))
    .header("Authorization", "Bearer sk-tg-你的密钥")
    .header("Content-Type", "application/json")
    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body))
    .build();
var resp = http.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());
// Node.js 18+ 内置 fetch,无需安装依赖
const resp = await fetch("http://localhost:8000/v1/chat/completions", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk-tg-你的密钥",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: "你好" }],
  }),
});
const data = await resp.json();
console.log(data.choices[0].message.content);
console.log(data.usage); // prompt_tokens / completion_tokens / total_tokens

认证方式

所有 /v1/* 接口通过 HTTP Header 携带 API Key:

Authorization: Bearer sk-tg-你的密钥

网页端管理接口(/api/*)使用登录会话 Cookie(tg_session);程序化调用可用系统访问令牌:Authorization: Bearer tg-sat-你的令牌 + New-Api-User: 你的用户ID(NewAPI 兼容)。

注意:sk-tg- 模型调用密钥仅用于 /v1/* 网关(以及 /api/usage/token、/api/log/token 两个自身查询端点),不能用于其他 /api/* 管理接口。

大语言模型(LLM)

POST/v1/chat/completions — 支持流式(stream:true)与非流式。

{
  "model": "gpt-4o-mini",
  "messages": [{"role": "user", "content": "你好"}],
  "stream": true
}

同时支持 POST/v1/completions 与 POST/v1/embeddings。

参数与限制

  • model(必填):通过 GET/v1/models?category=llm 拉取当前可用模型列表;平台自动路由到优先级最高的可用上游
  • messages(必填):OpenAI 兼容消息数组,role 支持 system / user / assistant / tool;content 支持纯文本或 [{"type":"text","text":"..."},{"type":"image_url","image_url":{"url":"..."}}] 多模态数组(具体取决于模型是否支持视觉)
  • stream:布尔值,默认 false;开启后按 SSE 流式返回
  • temperature:默认 1,范围 0–2;采样温度
  • top_p:默认 1,范围 0–1;核采样
  • max_tokens / max_completion_tokens:最大生成 token 数(模型上下文限制不同,详见模型列表)
  • frequency_penalty / presence_penalty:默认 0,范围 -2–2
  • stop:停止序列,字符串或字符串数组
  • tools / tool_choice:函数调用(模型支持时透传)
  • response_format:JSON Schema / JSON mode(模型支持时透传)
  • seed:固定随机种子(上游支持时生效)
  • 单条消息大小与总上下文长度受具体模型限制;请求体大小受 nginx 限制(1000MB)

模型列表字段

调用 GET/v1/models 时,每个模型对象除 id / display_name / category / price_display / sale_price_display 外,还包含:

  • description:模型说明,含【输入参数】【适用场景】【模型说明】【版本差异】等信息
  • capabilities:能力对象,如 {"vision": true}(保留字段)
  • supports_image:是否支持图片输入
  • supports_audio:是否支持音频输入
  • supports_video:是否支持视频输入

多模态输入请以 image_url / audio_url / video_url 形式放在 messages 数组中;当前模型不支持对应输入类型时,平台将按规则自动在同类可用模型中选择最低价的支持模型进行替换,并通过响应头 X-Switched-Model 返回实际使用的模型名。

Responses API

POST/v1/responses — 平台原生支持 OpenAI Responses API 格式,供使用新版 openai SDK(client.responses.create)或 Codex CLI 等客户端直接对接。请求在网关自动转换为 chat/completions 转发,计费与 chat/completions 完全一致。

curl http://localhost:8000/v1/responses \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -H "Content-Type: application/json" \
  -d '{"model":"glm-5.2","input":"用一句话介绍你自己"}'
import requests

# Python 用户也可以直接使用官方 openai SDK:
#   resp = client.responses.create(model="glm-5.2", input="用一句话介绍你自己")
r = requests.post(
    "http://localhost:8000/v1/responses",
    headers={"Authorization": "Bearer sk-tg-你的密钥"},
    json={"model": "glm-5.2", "input": "用一句话介绍你自己"},
    timeout=120,
)
data = r.json()
print(data["output"][0]["content"][0]["text"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"model\":\"glm-5.2\",\"input\":\"用一句话介绍你自己\"}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/v1/responses"))
    .header("Authorization", "Bearer sk-tg-你的密钥")
    .header("Content-Type", "application/json")
    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body))
    .build();
var resp = http.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());
// Node.js 18+
const resp = await fetch("http://localhost:8000/v1/responses", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk-tg-你的密钥",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ model: "glm-5.2", input: "用一句话介绍你自己" }),
});
const data = await resp.json();
console.log(data.output[0].content[0].text);

参数说明

  • model(必填):同 chat/completions 的模型列表
  • input(必填):字符串,或消息/工具调用对象数组(message / function_call / function_call_output);消息 content 支持纯文本或 [{"type":"input_text",...},{"type":"input_image","image_url":...}] 多模态数组
  • instructions:等价于 system 消息,自动前置
  • max_output_tokens → 映射为 max_tokens
  • tools / tool_choice:支持 function 类型工具(格式自动转换)
  • temperature / top_p / parallel_tool_calls:直传
  • reasoning.effort → 尽力映射为 reasoning_effort(上游不识别时可能报错)
  • stream:流式开关,开启后按 Responses API 事件序列返回 SSE
  • store / metadata / include / truncation:忽略

流式事件序列

开启 stream:true 后,按顺序返回以下 SSE 事件(每事件含递增的 sequence_number):

  • response.created — 响应已创建(status: in_progress)
  • response.output_item.added — 新输出项(message / function_call / reasoning)
  • response.content_part.added — 消息内容分块开始(output_text)
  • response.output_text.delta — 文本增量
  • response.reasoning_summary_text.delta — 思考模型推理增量(如有)
  • response.function_call_arguments.delta — 工具调用参数增量(如有)
  • response.output_text.done / response.content_part.done / response.output_item.done — 逐级闭合
  • response.completed — 完成事件,含完整输出与 usage(input_tokens / output_tokens)

限制说明

  • 无状态转发:不支持 previous_response_id / conversation(请在 input 中携带完整历史),不支持 background 后台任务
  • 不支持内置工具(web_search / file_search / computer_use / code_interpreter / mcp)与 input_file 文件输入
  • 多轮工具对话的 call_id 历史回放为有损转换;深度 Agent 场景建议使用 /v1/chat/completions

图片生成

图片能力统一入口为 POST/v1/cap/image/{path}。调用方只需指定 model,平台根据模型自动路由到对应上游(SnapGen 原生生图接口,或 OpenRouter 多模态 LLM 的 chat.completions)。

  • POST/v1/cap/image/generate_image — 文生图 / 图生图 / 图片编辑(form-data)
  • GET/v1/cap/image/history/{uuid} — 查询单条生成结果(status == 2 即完成)
  • GET/v1/cap/image/histories — 历史列表
  • GET/v1/cap/tasks/{task_id} — 查询能力路由异步任务状态及结果(返回 task_id 时使用)

模型可选项

已接入的图片生成模型覆盖原生生图模型(nano-banana-pro / nano-banana-2 / nano-banana-2-lite / grok-image / gpt-image-2)以及多模态 LLM 生图模型(grok / gemini / inkling 等)。完整、实时列表请通过 GET/v1/models?category=image 拉取;支持多分类交集查询,如 ?category=image&category=llm。未在列表中的模型无法通过本能力路由调用。

不同模型的返回形式由平台统一处理:本身已提供异步任务标识的模型返回 {"uuid": "..."},需轮询 /v1/cap/image/history/{uuid} 获取结果;上游为同步返回的模型由网关自动包装为异步任务,返回 {"task_id": "..."},需调用 GET /v1/cap/tasks/{task_id} 获取结果 URL。下游按返回字段选择对应的查询方式即可。

文生图

curl http://localhost:8000/v1/cap/image/generate_image \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -F "prompt=a cute cat on a windowsill" \
  -F "model=nano-banana-2-lite" \
  -F "aspect_ratio=1:1"
import requests

# form-data 提交文生图任务;返回 {"uuid": "..."},用 uuid 轮询结果
r = requests.post(
    "http://localhost:8000/v1/cap/image/generate_image",
    headers={"Authorization": "Bearer sk-tg-你的密钥"},
    data={"prompt": "a cute cat on a windowsill",
          "model": "nano-banana-2-lite",
          "aspect_ratio": "1:1"},
    timeout=120,
)
print(r.json())  # {"uuid": "9f8e7d6c-xxxx-..."}
// JDK 11+:multipart/form-data 需手动拼 boundary
var http = java.net.http.HttpClient.newHttpClient();
var boundary = "----TokenGet" + System.currentTimeMillis();
var body = java.net.http.HttpRequest.BodyPublishers.ofString(
    "--" + boundary + "\r\n"
    + "Content-Disposition: form-data; name=\"prompt\"\r\n\r\n"
    + "a cute cat on a windowsill\r\n"
    + "--" + boundary + "\r\n"
    + "Content-Disposition: form-data; name=\"model\"\r\n\r\n"
    + "nano-banana-2-lite\r\n"
    + "--" + boundary + "\r\n"
    + "Content-Disposition: form-data; name=\"aspect_ratio\"\r\n\r\n"
    + "1:1\r\n"
    + "--" + boundary + "--\r\n");
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/v1/cap/image/generate_image"))
    .header("Authorization", "Bearer sk-tg-你的密钥")
    .header("Content-Type", "multipart/form-data; boundary=" + boundary)
    .POST(body).build();
var resp = http.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());  // {"uuid": "..."}
// Node.js 18+:用 FormData 构造 multipart 请求
const fd = new FormData();
fd.append("prompt", "a cute cat on a windowsill");
fd.append("model", "nano-banana-2-lite");
fd.append("aspect_ratio", "1:1");
const resp = await fetch("http://localhost:8000/v1/cap/image/generate_image", {
  method: "POST",
  headers: { Authorization: "Bearer sk-tg-你的密钥" },
  body: fd,
});
console.log(await resp.json()); // {"uuid": "..."}

图生图 / 图片编辑

增加 image 参数传入参考图 URL,即可图生图或风格迁移:

curl http://localhost:8000/v1/cap/image/generate_image \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -F "prompt=turn this into a watercolor painting" \
  -F "model=nano-banana-2-lite" \
  -F "image=https://example.com/photo.jpg" \
  -F "aspect_ratio=1:1"

本地图片直接上传(multipart/form-data)

参考图也可以本地文件随请求直接上传——image 字段值用 @文件路径,与表单字段同一请求发出,平台不设大小限制(仅网关本身的请求体上限):

curl http://localhost:8000/v1/cap/image/generate_image \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -F "prompt=把这张照片变成水彩画" \
  -F "model=nano-banana-2-lite" \
  -F "image=@/Users/me/photo.jpg"

URL 与本地文件可混用;多张参考图重复传 image 字段即可。

多图参考

form-data 支持同名字段重复传参——多图参考时重复传多个 image 字段(是否生效取决于所选模型):

curl http://localhost:8000/v1/cap/image/generate_image \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -F "prompt=把图1的人物放进图2的场景,保持图3的画风" \
  -F "model=nano-banana-2-lite" \
  -F "image=https://example.com/person.jpg" \
  -F "image=https://example.com/scene.jpg" \
  -F "image=https://example.com/style.jpg"

参数说明

  • prompt(必填):画面描述 / 编辑指令
  • model(必填):生图模型;当前可选项为 nano-banana-2-lite,实时完整列表通过 GET/v1/models?category=image 获取
  • aspect_ratio:宽高比,如 1:1 / 16:9 / 9:16
  • image:参考图,支持公网 URL 或 @本地文件路径 直接上传,可重复传多张(数量受所选模型限制)
  • image_width / image_height:输出尺寸(可选)
  • quality:hd 提升质量(可选)
  • seed:固定随机种子(可选)

轮询结果

curl http://localhost:8000/v1/cap/image/history/9f8e7d6c-xxxx \
  -H "Authorization: Bearer sk-tg-你的密钥"
import time
import requests

# status == 2 表示完成,从响应中取结果图 URL;建议每 5–10 秒轮询一次
uuid = "9f8e7d6c-xxxx"
while True:
    r = requests.get(
        f"http://localhost:8000/v1/cap/image/history/{uuid}",
        headers={"Authorization": "Bearer sk-tg-你的密钥"},
        timeout=30,
    )
    data = r.json()
    print(data.get("status"), data.get("progress", ""))
    if data.get("status") == 2:   # 完成
        print(data)               # 结果图 URL 在响应中
        break
    if data.get("status") == 3:   # 失败
        raise RuntimeError(data)
    time.sleep(5)
// JDK 11+:每 5 秒轮询一次,status == 2 表示完成
var http = java.net.http.HttpClient.newHttpClient();
var uuid = "9f8e7d6c-xxxx";
while (true) {
    var req = java.net.http.HttpRequest.newBuilder()
        .uri(java.net.URI.create("http://localhost:8000/v1/cap/image/history/" + uuid))
        .header("Authorization", "Bearer sk-tg-你的密钥")
        .GET().build();
    var resp = http.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());
    System.out.println(resp.body());
    if (resp.body().contains("\"status\":2") || resp.body().contains("\"status\": 2")) {
        break;   // 完成,结果图 URL 在响应中
    }
    Thread.sleep(5000);
}
// Node.js 18+:每 5 秒轮询一次,status === 2 表示完成
const uuid = "9f8e7d6c-xxxx";
while (true) {
  const resp = await fetch(
    `http://localhost:8000/v1/cap/image/history/${uuid}`,
    { headers: { Authorization: "Bearer sk-tg-你的密钥" } },
  );
  const data = await resp.json();
  console.log(data.status, data.progress ?? "");
  if (data.status === 2) { console.log(data); break; } // 完成
  if (data.status === 3) throw new Error(JSON.stringify(data)); // 失败
  await new Promise((r) => setTimeout(r, 5000));
}

status == 2 表示完成,从响应中取结果图 URL;建议每 5–10 秒轮询一次。

异步任务包装

图片能力路由会在网关层自动完成异步任务封装:若上游图片接口为同步返回,平台会自动将其包装为任务,下游仍按原有方式调用,无需关心上游实现差异。调用成功后将返回任务标识 {"task_id": "..."}:

curl http://localhost:8000/v1/cap/image/generate_image \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -F "prompt=a cute cat" \
  -F "model=你的生图模型"

拿到 task_id 后,通过 GET/v1/cap/tasks/{task_id} 查询结果:

curl http://localhost:8000/v1/cap/tasks/task-xxx \
  -H "Authorization: Bearer sk-tg-你的密钥"

返回示例:

{
  "task_id": "task-...",
  "status": "completed",
  "category": "image",
  "result_urls": ["https://..."],
  "upstream_response": { ... },
  "error": null,
  "created_at": "...",
  "completed_at": "..."
}

status 为 completed 时,图片 URL 在 result_urls 数组中;若上游调用失败则 status 为 failed,error 字段携带失败原因。该封装由 TokenGet 自动处理,下游接口路径与调用方式保持不变。

OpenAI 兼容图片生成

除能力路由外,平台还提供 OpenAI 兼容端点 POST/v1/images/generations。调用方用 JSON 传参,网关自动把请求路由到对应上游;若上游原生协议为 multipart/form-data,网关会在后台自动完成协议转换。

curl http://localhost:8000/v1/images/generations \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-1",
    "prompt": "a cute cat on a windowsill",
    "aspect_ratio": "1:1"
  }'

也支持以 multipart/form-data 直接调用,字段与上游原生接口一致:

curl http://localhost:8000/v1/images/generations \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -F "model=grok-imagine-2.0" \
  -F "prompt=a futuristic city at night" \
  -F "aspect_ratio=16:9" \
  -F "image=@/Users/me/ref.jpg"

可用模型请通过 GET/v1/models?category=image 实时获取,例如 gpt-image-1 / gpt-image-1.5 / grok-imagine-2.0 / nana-banana / nana-banana-pro 等。返回形式由平台统一处理:若返回 {"uuid":"..."},需轮询 /v1/cap/image/history/{uuid};若返回 {"task_id":"..."},需调用 GET /v1/cap/tasks/{task_id} 获取结果。

视频生成

视频能力统一入口为 POST/v1/cap/video/{path}。调用方只需指定 model,平台根据模型自动路由到对应上游(SnapGen / MiniMax 原生视频接口,或 OpenRouter 多模态 LLM 的 chat.completions)。

  • POST/v1/cap/video/v2/video_generation — 提交视频生成任务
  • GET/v1/cap/video/v2/query/video_generation/{task_id} — 查询任务状态

模型可选项

已接入的视频生成模型包括:

  • SnapGen 原生视频:flux-3 / veo-3
  • MiniMax 原生视频:MiniMax-H3(全功能,768P/2K,4–15s)/ MiniMax-H3-Max(更快,480P/768P,5–15s)
  • OpenRouter 多模态 LLM:grok / gemini / inkling 等

完整、实时的 model 列表请通过 GET/v1/models?category=video 拉取;支持多分类交集查询,如 ?category=video&category=llm。未在列表中的模型无法通过本能力路由调用。

不同上游的返回格式不同:SnapGen / MiniMax 原生模型返回任务 ID 需轮询;OpenRouter 多模态 LLM 返回 chat.completion 格式(assistant message 中携带视频 URL)。

1. 文生视频

content 仅含文本;ratio 必填(不能为 adaptive)。提示词支持运镜指令如 [推进]:

curl http://localhost:8000/v1/cap/video/v2/video_generation \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MiniMax-H3",
    "content": [{"type": "text", "text": "一只橘猫在窗台上伸懒腰,阳光洒进房间,[推进]"}],
    "resolution": "768P",
    "duration": 6,
    "ratio": "16:9"
  }'
import requests

# 提交任务,返回 task_id,随后轮询查询
r = requests.post(
    "http://localhost:8000/v1/cap/video/v2/video_generation",
    headers={"Authorization": "Bearer sk-tg-你的密钥"},
    json={
        "model": "MiniMax-H3",
        "content": [{"type": "text", "text": "一只橘猫在窗台上伸懒腰,阳光洒进房间,[推进]"}],
        "resolution": "768P",
        "duration": 6,
        "ratio": "16:9",
    },
    timeout=120,
)
print(r.json())  # {"task_id": "424010985738629", ...}
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = """
    {"model":"MiniMax-H3",
     "content":[{"type":"text","text":"一只橘猫在窗台上伸懒腰,阳光洒进房间,[推进]"}],
     "resolution":"768P","duration":6,"ratio":"16:9"}
    """.strip().replaceAll("\n\\s*", "");
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/v1/cap/video/v2/video_generation"))
    .header("Authorization", "Bearer sk-tg-你的密钥")
    .header("Content-Type", "application/json")
    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body))
    .build();
var resp = http.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());  // {"task_id": "..."}
// Node.js 18+
const resp = await fetch(
  "http://localhost:8000/v1/cap/video/v2/video_generation",
  {
    method: "POST",
    headers: {
      Authorization: "Bearer sk-tg-你的密钥",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "MiniMax-H3",
      content: [{ type: "text", text: "一只橘猫在窗台上伸懒腰,阳光洒进房间,[推进]" }],
      resolution: "768P",
      duration: 6,
      ratio: "16:9",
    }),
  },
);
console.log(await resp.json()); // {"task_id": "..."}

2. 图生视频(首帧)

图片作为视频起始画面;ratio 由图片决定(恒为 adaptive,可不传):

curl http://localhost:8000/v1/cap/video/v2/video_generation \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MiniMax-H3",
    "content": [
      {"type": "text", "text": "一只橘猫在窗台上伸懒腰,[推进]"},
      {"type": "image_url", "image_url": {"url": "https://example.com/cat.jpg"}, "role": "first_frame"}
    ],
    "resolution": "768P",
    "duration": 6
  }'

3. 首尾帧视频

同时指定开始与结束画面(first_frame + last_frame),模型生成中间过渡:

curl http://localhost:8000/v1/cap/video/v2/video_generation \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MiniMax-H3",
    "content": [
      {"type": "text", "text": "猫咪从窗台轻盈跳到沙发上"},
      {"type": "image_url", "image_url": {"url": "https://example.com/start.jpg"}, "role": "first_frame"},
      {"type": "image_url", "image_url": {"url": "https://example.com/end.jpg"}, "role": "last_frame"}
    ],
    "resolution": "768P",
    "duration": 6
  }'

4. 多参视频(图片 + 声音 + 视频参数)

多模态参考生视频:参考图定角色/风格(最多 9 张)+ 参考视频定动作(最多 3 段)+ 参考音频定声音(最多 3 段),按需组合:

curl http://localhost:8000/v1/cap/video/v2/video_generation \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MiniMax-H3",
    "content": [
      {"type": "text", "text": "宇航员在月球表面漫步,背景是浩瀚星空"},
      {"type": "image_url", "image_url": {"url": "https://example.com/astronaut.jpg"}, "role": "reference_image"},
      {"type": "image_url", "image_url": {"url": "https://example.com/style.jpg"}, "role": "reference_image"},
      {"type": "video_url", "video_url": {"url": "https://example.com/walk.mp4"}, "role": "reference_video"},
      {"type": "audio_url", "audio_url": {"url": "https://example.com/space.mp3"}, "role": "reference_audio"}
    ],
    "resolution": "2K",
    "duration": 8,
    "ratio": "16:9"
  }'

5. 视频编辑

把待编辑视频作为 reference_video 传入,用文本描述修改需求(换风格、换背景、改动作、加特效等);可同时传 reference_image 指定新形象、reference_audio 指定配音:

curl http://localhost:8000/v1/cap/video/v2/video_generation \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "MiniMax-H3",
    "content": [
      {"type": "text", "text": "把画面变成赛博朋克风格,霓虹灯光,雨夜氛围"},
      {"type": "video_url", "video_url": {"url": "https://example.com/source.mp4"}, "role": "reference_video"}
    ],
    "resolution": "768P",
    "duration": 6
  }'

6. 本地文件直接上传(multipart/form-data)

除 JSON 外,视频接口也接受 multipart/form-data:表单字段与文件同一请求发出,文件值用 @本地路径,平台自动转存并生成 URL 发给上游,平台不设文件大小限制(仅网关本身的请求体上限)。字段名与 JSON content 角色的对应关系:

  • prompt 或 text → 文本项
  • first_frame / last_frame → 首帧 / 尾帧图片
  • image(或 reference_image)→ 参考图,可重复传多张
  • video(或 reference_video)→ 参考视频,可重复传多段
  • audio(或 reference_audio)→ 参考音频,可重复传多段
  • 其余字段(model / resolution / duration / ratio 等)原样作为顶层参数

图生视频(本地首帧图):

curl http://localhost:8000/v1/cap/video/v2/video_generation \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -F "model=MiniMax-H3" \
  -F "prompt=一只橘猫在窗台上伸懒腰,[推进]" \
  -F "first_frame=@/Users/me/cat.jpg" \
  -F "resolution=768P" \
  -F "duration=6"

多参视频(本地参考图 + 参考视频 + 参考音频混合,也可与 URL 混用):

curl http://localhost:8000/v1/cap/video/v2/video_generation \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -F "model=MiniMax-H3" \
  -F "prompt=宇航员在月球表面漫步,背景是浩瀚星空" \
  -F "image=@/Users/me/astronaut.jpg" \
  -F "image=https://example.com/style.jpg" \
  -F "video=@/Users/me/walk.mp4" \
  -F "audio=@/Users/me/space.mp3" \
  -F "resolution=2K" \
  -F "duration=8" \
  -F "ratio=16:9"

7. 轮询任务

curl http://localhost:8000/v1/cap/video/v2/query/video_generation/424010985738629 \
  -H "Authorization: Bearer sk-tg-你的密钥"
import time
import requests

# 建议每 10 秒轮询一次;task.status == "succeeded" 时,
# task.content.url 即为成片下载地址
task_id = "424010985738629"
while True:
    r = requests.get(
        f"http://localhost:8000/v1/cap/video/v2/query/video_generation/{task_id}",
        headers={"Authorization": "Bearer sk-tg-你的密钥"},
        timeout=30,
    )
    task = r.json()["task"]
    print(task["status"], task.get("progress", ""))
    if task["status"] == "succeeded":
        print(task["content"]["url"])   # 成片下载地址
        break
    time.sleep(10)
// JDK 11+:每 10 秒轮询一次,task.status == "succeeded" 时成片在 task.content.url
var http = java.net.http.HttpClient.newHttpClient();
var taskId = "424010985738629";
while (true) {
    var req = java.net.http.HttpRequest.newBuilder()
        .uri(java.net.URI.create(
            "http://localhost:8000/v1/cap/video/v2/query/video_generation/" + taskId))
        .header("Authorization", "Bearer sk-tg-你的密钥")
        .GET().build();
    var resp = http.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());
    System.out.println(resp.body());
    if (resp.body().contains("\"status\":\"succeed\"")
            || resp.body().contains("\"status\": \"succeed\"")) {
        break;   // 解析 task.content.url 取成片地址
    }
    Thread.sleep(10000);
}
// Node.js 18+:每 10 秒轮询一次
const taskId = "424010985738629";
while (true) {
  const resp = await fetch(
    `http://localhost:8000/v1/cap/video/v2/query/video_generation/${taskId}`,
    { headers: { Authorization: "Bearer sk-tg-你的密钥" } },
  );
  const { task } = await resp.json();
  console.log(task.status, task.progress ?? "");
  if (task.status === "succeed" || task.status === "succeeded") {
    console.log(task.content.url); // 成片下载地址
    break;
  }
  await new Promise((r) => setTimeout(r, 10000));
}

建议每 10 秒轮询一次。task.status 为 succeeded 时,task.content.url 即为成片下载地址。

参数与限制速查

  • model(必填):视频生成模型;可选项见上方“模型可选项”,实时完整列表通过 GET/v1/models?category=video 获取
  • content[].type:text / image_url / video_url / audio_url
  • content[].role:first_frame / last_frame / reference_image / reference_video / reference_audio
  • resolution:480P / 768P / 2K(视模型而定);duration:秒数
  • ratio:文生必填(21:9/16:9/4:3/1:1/3:4/9:16);图生由图片决定;多参可选默认 adaptive
  • 参考数量上限按所选模型配置并由平台强制校验(超限返回 400):如 MiniMax-H3 为图片 ≤ 9 张、视频 ≤ 3 段(每段 2–15s、总 ≤ 15s)、音频 ≤ 3 段;本地文件上传仅受网关请求体上限约束(大文件建议走 multipart 直传或公网 URL,勿用 Base64)
  • 图生与多参互斥:content 中出现 reference_* 就不能再用 first_frame/last_frame
  • 素材要求:图片 JPG/PNG/WEBP ≤ 30MB(256–5760px,宽高比 0.4–2.5);视频 MP4/MOV(H.264/H.265)≤ 50MB;音频 WAV/MP3 ≤ 15MB
  • callback_url:可选,任务状态变更时回调通知

OpenAI 兼容视频生成

平台同时提供 OpenAI 兼容端点 POST/v1/videos。调用方用 JSON 传参,网关自动路由到对应上游;若上游原生协议为 multipart/form-data(如 SnapGen),网关会在后台自动完成协议转换。

curl http://localhost:8000/v1/videos \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "seedance-2-0-mini",
    "prompt": "an astronaut walking on the moon, cinematic lighting",
    "ratio": "16:9",
    "duration": 6
  }'

也支持以 multipart/form-data 直接调用:

curl http://localhost:8000/v1/videos \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -F "model=grok-imagine-video" \
  -F "prompt=a robot dancing in neon lights" \
  -F "ratio=9:16" \
  -F "duration=5" \
  -F "first_frame=@/Users/me/start.jpg"

可用模型请通过 GET/v1/models?category=video 实时获取,例如 seedance-2-0-mini / seedance-2.0-fast / grok-imagine-video 等。返回格式遵循上游原生响应(SnapGen 返回任务 ID,需轮询对应历史接口获取成片 URL)。

GoAnyAI 供应商与分组

GoAnyAI 已作为独立供应商接入。其模型按官方 marketplace 的分组拆分为多个对外模型名,格式为 {模型名}--{分组名};不传分组时可直接使用基础模型名,系统会路由到默认(最低价)分组。

  • 基础模型名(默认分组):claude-fable-5 / gpt-image-2 / grok-imagine-video 等
  • 指定分组:claude-fable-5--AWS-Claude-分组 / gpt-image-2--特价--image-1k 等
  • 价格来自 GoAnyAI 官方 marketplace,按人民币成本计价,再乘以平台售价系数后向下游展示

调用方式与普通模型一致,例如指定分组生成图片:

curl http://localhost:8000/v1/images/generations \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2--特价--image-1k",
    "prompt": "a cute cat on a windowsill"
  }'

或指定分组进行对话:

curl http://localhost:8000/v1/chat/completions \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-4-5--kiro-Claude",
    "messages": [{"role": "user", "content": "Hello"}]
  }'

管理员可在后台「上游供应商」页看到 goanyai,点击「同步 GoAnyAI」即可一键拉取最新模型、分组与价格。

语音合成

本平台通过统一的能力路由 POST/v1/cap/speech/{上游路径} 接入两家语音供应商,按 model(MiniMax 系列)或 model_id(ElevenLabs 系列)前缀自动分派。返回为音频二进制流或 JSON 包装音频,直接透传上游。

1. MiniMax TTS

POST/v1/cap/speech/v1/t2a_v2 — 返回 JSON,音频在 data.audio(hex 编码),字符用量在 extra_info.usage_characters。

curl http://localhost:8000/v1/cap/speech/v1/t2a_v2 \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "speech-2.8-hd",
    "text": "你好,欢迎使用 TokenGet。",
    "voice_setting": {
      "voice_id": "female-shaonv",
      "speed": 1,
      "vol": 1,
      "pitch": 0,
      "emotion": "happy"
    },
    "audio_setting": {
      "sample_rate": 32000,
      "bitrate": 128000,
      "format": "mp3",
      "channel": 1
    }
  }'
import requests

r = requests.post(
    "http://localhost:8000/v1/cap/speech/v1/t2a_v2",
    headers={"Authorization": "Bearer sk-tg-你的密钥"},
    json={
        "model": "speech-2.8-hd",
        "text": "你好,欢迎使用 TokenGet。",
        "voice_setting": {
            "voice_id": "female-shaonv",
            "speed": 1,
            "vol": 1,
            "pitch": 0,
            "emotion": "happy",
        },
        "audio_setting": {
            "sample_rate": 32000,
            "bitrate": 128000,
            "format": "mp3",
            "channel": 1,
        },
    },
    timeout=120,
)
data = r.json()
audio_hex = data["data"]["audio"]            # 音频内容(hex 编码)
usage = data["extra_info"]["usage_characters"]  # 计费字符数
with open("speech.mp3", "wb") as f:
    f.write(bytes.fromhex(audio_hex))       # 解码并保存为 mp3
// JDK 11+:data.audio 为 hex 编码,解码后写文件
var http = java.net.http.HttpClient.newHttpClient();
var body = """
    {"model":"speech-2.8-hd","text":"你好,欢迎使用 TokenGet。",
     "voice_setting":{"voice_id":"female-shaonv","speed":1,"vol":1,"pitch":0,"emotion":"happy"},
     "audio_setting":{"sample_rate":32000,"bitrate":128000,"format":"mp3","channel":1}}
    """.strip().replaceAll("\n\\s*", "");
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/v1/cap/speech/v1/t2a_v2"))
    .header("Authorization", "Bearer sk-tg-你的密钥")
    .header("Content-Type", "application/json")
    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body))
    .build();
var resp = http.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());  // 解析 data.audio(hex)后写 mp3 文件
// Node.js 18+
import { writeFile } from "node:fs/promises";

const resp = await fetch("http://localhost:8000/v1/cap/speech/v1/t2a_v2", {
  method: "POST",
  headers: {
    Authorization: "Bearer sk-tg-你的密钥",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "speech-2.8-hd",
    text: "你好,欢迎使用 TokenGet。",
    voice_setting: {
      voice_id: "female-shaonv",
      speed: 1,
      vol: 1,
      pitch: 0,
      emotion: "happy",
    },
    audio_setting: { sample_rate: 32000, bitrate: 128000, format: "mp3", channel: 1 },
  }),
});
const data = await resp.json();
await writeFile("speech.mp3", Buffer.from(data.data.audio, "hex"));

MiniMax 模型可选项

  • speech-2.8-hd / speech-2.8-turbo
  • speech-2.6-hd / speech-2.6-turbo
  • speech-02-hd / speech-02-turbo
  • speech-01-hd / speech-01-turbo

MiniMax 参数说明

  • model(必填):从上方可选项中选择
  • text(必填):要合成的文本,单条长度受模型限制(通常 ≤ 10000 字符)
  • voice_setting(必填):
    • voice_id:音色 ID,如 female-shaonv、male-qnqingse 等(以 MiniMax 官方更新为准)
    • speed:语速,默认 1,建议范围 0.5–2.0
    • vol:音量,默认 1,建议范围 0–10
    • pitch:音调,默认 0,建议范围 -12–12
    • emotion:情绪,如 happy / sad / neutral 等(可选,模型支持时生效)
  • audio_setting(必填):
    • sample_rate:采样率,可选 16000 / 24000 / 32000 / 44100 / 48000
    • bitrate:码率,如 64000 / 128000 / 192000
    • format:输出格式,mp3 / wav / pcm / ogg
    • channel:声道数,1(单声道)或 2(立体声)
  • 计费:按请求文本字符数 × 1.5 系数扣积分;失败请求不计费

2. ElevenLabs TTS

POST/v1/cap/speech/v1/text-to-speech/{voice_id} — 直接返回 audio/mpeg 二进制音频。

curl http://localhost:8000/v1/cap/speech/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb \
  -H "Authorization: Bearer sk-tg-你的密钥" \
  -H "Content-Type: application/json" \
  -o speech.mp3 \
  -d '{
    "model_id": "eleven_multilingual_v2",
    "text": "Hello, welcome to TokenGet.",
    "voice_settings": {
      "stability": 0.5,
      "similarity_boost": 0.75,
      "style": 0.0,
      "use_speaker_boost": true
    }
  }'

ElevenLabs 模型可选项

  • eleven_v3(质量最高,支持多语言)
  • eleven_multilingual_v2 / eleven_multilingual_v1
  • eleven_flash_v2_5 / eleven_flash_v2(低延迟)
  • eleven_turbo_v2_5 / eleven_turbo_v2
  • eleven_monolingual_v1

ElevenLabs 参数说明

  • model_id(必填):从上方可选项中选择
  • text(必填):要合成的文本
  • voice_id(路径参数):目标音色 ID,如 JBFqnCBsd6RMkjVDRZzb;可通过上游官方接口 /v1/voices 获取
  • voice_settings(可选):
    • stability:稳定性,0–1
    • similarity_boost:相似度增强,0–1
    • style:风格强度,0–1(部分模型支持)
    • use_speaker_boost:布尔值
  • pronunciation_dictionary_locators / seed / previous_text / next_text:可选高级参数(上游支持时透传)
  • 计费:按请求文本字符数 × 1.5 系数扣积分;失败请求不计费

3. 其他 ElevenLabs 能力

通过 /v1/cap/speech/v1/... 直接透传,参数与官方一致:

  • POST/v1/cap/speech/v1/text-to-voice/create-previews — 按文本描述生成预览音色(text 需 ≥ 100 字符)
  • POST/v1/cap/speech/v1/text-to-voice/create-voice-from-preview — 将预览音色保存为永久音色
  • POST/v1/cap/speech/v1/sound-generation — 音效生成
  • POST/v1/cap/speech/v1/voice_clone / POST/v1/cap/speech/v1/files/upload / POST/v1/cap/speech/v1/voices/add — 自定义音色相关(按上游官方 API 传参)

语音通用说明

  • 当前仅支持语音合成(TTS),暂未接入语音识别(STT)与音乐生成
  • 模型可选项以 GET/v1/models?category=speech 实时返回为准;未列出的 model/model_id 会路由失败
  • 请求体大小受 nginx 限制(1000MB)

文件接口

用户文件默认保存 2 天(管理员可按用户调整)。文件接口使用登录会话认证(浏览器 Cookie),程序化调用可用 tg-sat- 系统访问令牌。

  • POST/api/file/ — multipart 上传,返回 file_id
  • GET/api/file/ — 文件列表
  • GET/api/file/{file_id} — 下载
  • DELETE/api/file/{file_id} — 删除
TOKEN=<tg-sat-系统访问令牌>; UID=<你的用户ID>
# 上传(返回 file_id)
curl http://localhost:8000/api/file/ -H "Authorization: Bearer $TOKEN" -H "New-Api-User: $UID" -F "file=@photo.jpg"
# 列表
curl http://localhost:8000/api/file/ -H "Authorization: Bearer $TOKEN" -H "New-Api-User: $UID"
# 下载
curl http://localhost:8000/api/file/<file_id> -H "Authorization: Bearer $TOKEN" -H "New-Api-User: $UID" -o photo.jpg
# 删除
curl -X DELETE http://localhost:8000/api/file/<file_id> -H "Authorization: Bearer $TOKEN" -H "New-Api-User: $UID"
import requests

BASE = "http://localhost:8000/api/file"
# Bearer 令牌调用 /api/* 需同时携带 New-Api-User(用户 ID)头
HDRS = {"Authorization": "Bearer <tg-sat-系统访问令牌>",
        "New-Api-User": "<你的用户ID>"}

# 上传:multipart 字段名固定为 file,返回 file_id(默认保留 2 天)
with open("photo.jpg", "rb") as f:
    r = requests.post(BASE + "/", headers=HDRS,
                      files={"file": ("photo.jpg", f, "image/jpeg")},
                      data={"purpose": "general"}, timeout=120)
file_id = r.json()["data"]["file_id"]

# 列表
r = requests.get(BASE + "/", headers=HDRS, params={"page": 1, "page_size": 20})
print(r.json()["data"]["items"])

# 下载
r = requests.get(f"{BASE}/{file_id}", headers=HDRS, timeout=120)
with open("photo_copy.jpg", "wb") as f:
    f.write(r.content)

# 删除
r = requests.delete(f"{BASE}/{file_id}", headers=HDRS, timeout=30)
print(r.json())  # {"success": true, "data": {"ok": true}}
// JDK 11+:上传需手动拼 multipart/form-data
var http = java.net.http.HttpClient.newHttpClient();
var token = "<tg-sat-系统访问令牌>";
var uid = "<你的用户ID>";
var boundary = "----TokenGet" + System.currentTimeMillis();
var fileBytes = java.nio.file.Files.readAllBytes(java.nio.file.Path.of("photo.jpg"));
var body = java.net.http.HttpRequest.BodyPublishers.ofByteArrays(java.util.List.of(
    ("--" + boundary + "\r\nContent-Disposition: form-data; name=\"file\"; "
     + "filename=\"photo.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n").getBytes(),
    fileBytes,
    ("\r\n--" + boundary + "--\r\n").getBytes()));
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/file/"))
    .header("Authorization", "Bearer " + token)
    .header("New-Api-User", uid)
    .header("Content-Type", "multipart/form-data; boundary=" + boundary)
    .POST(body).build();
var resp = http.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());  // data.file_id 即下载/删除用的 ID
// Node.js 18+:上传 + 下载
import { readFile, writeFile } from "node:fs/promises";

const BASE = "http://localhost:8000/api/file";
// Bearer 令牌调用 /api/* 需同时携带 New-Api-User(用户 ID)头
const HDRS = {
  Authorization: "Bearer <tg-sat-系统访问令牌>",
  "New-Api-User": "<你的用户ID>",
};

// 上传
const fd = new FormData();
fd.append("file", new Blob([await readFile("photo.jpg")]), "photo.jpg");
fd.append("purpose", "general");
let resp = await fetch(BASE + "/", { method: "POST", headers: HDRS, body: fd });
const { data: { file_id } } = await resp.json();

// 列表
resp = await fetch(BASE + "/", { headers: HDRS });
console.log((await resp.json()).data.items);

// 下载
resp = await fetch(`${BASE}/${file_id}`, { headers: HDRS });
await writeFile("photo_copy.jpg", Buffer.from(await resp.arrayBuffer()));

// 删除
resp = await fetch(`${BASE}/${file_id}`, { method: "DELETE", headers: HDRS });
console.log(await resp.json()); // {"success": true, "data": {"ok": true}}

参数说明

  • file(上传必填):multipart 文件字段;无格式限制,大小仅受 nginx 限制(1000MB)
  • purpose(可选):文件用途标签,如 general / fine-tune / relay;不传默认 general
  • page / page_size(列表可选):分页,默认第 1 页,每页 20 条,最大 100 条
  • 文件默认保留 2 天;管理员可通过 PUT/api/user/(body 带 id)调整 file_retention_days

计费说明

  • 按调用量计费:每次成功调用按实际用量扣减积分,明细可在控制台「积分流水 / 调用记录」查询。
  • 失败的请求(HTTP ≥ 400)不计费。
  • 余额不足(≤ 0)时请求会被拒绝(HTTP 402),充值后立即恢复。
  • 新用户注册即送体验额度,可直接开始调用。

记录查询

  • GET/api/user/self — 当前余额(含账户信息)
  • GET/api/transaction/self?type=consume&page=1 — 积分流水(充值/消费/授予)
  • GET/api/log/self?category=llm&p=1 — API 调用记录(默认保留 2 天)
SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>; UTOKEN=<tg-usr-用户令牌>
curl http://localhost:8000/api/user/self -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
curl "http://localhost:8000/api/transaction/self?type=consume&page=1" -H "Authorization: Bearer $UTOKEN"
curl "http://localhost:8000/api/log/self?category=llm&p=1" -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"

组合调用示例(Workflows)

接口介绍是分开写的,但实际业务往往需要按先后顺序组合调用:前一步的返回值(令牌、task_id、file_id 等)是后一步的入参。下面给出四个最常用的组合工作流,每步注释标号 1/2/3,可直接照抄运行。

工作流 1:创建 Key → 调用 LLM → 核对扣费(调用方接入)

顺序依赖:① 先用系统访问令牌(tg-sat-)创建 API Key,响应里的 data.key 就是模型调用密钥(sk-tg-);② 用这个新 Key 调用 /v1/chat/completions;③ 调用成功后扣费流水才会生成,此时再查积分流水(tg-usr- 用户令牌,需 user:finance:read 权限)与余额核对。若顺序颠倒(先查流水再调用),将看不到本次扣费记录。

SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>; UTOKEN=<tg-usr-用户令牌>

# 1. 创建 API Key 令牌(响应 data.key 即 sk-tg- 密钥)
curl -X POST http://localhost:8000/api/token/ \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-key","remain_quota":500000}'

# 2. 用返回的 sk-tg- Key 调用 LLM
curl http://localhost:8000/v1/chat/completions \
  -H "Authorization: Bearer sk-tg-上一步返回的key" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"你好"}]}'

# 3. 查询积分流水,核对本次扣费(type=consume 为消费记录)
curl "http://localhost:8000/api/transaction/self?type=consume&page=1" \
  -H "Authorization: Bearer $UTOKEN"

# 4. 查询余额确认(data.balance 扣减后的值)
curl http://localhost:8000/api/user/self \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests

SAT = "<tg-sat-系统访问令牌>"
UID = "<你的用户ID>"
UTOKEN = "<tg-usr-用户令牌>"
ADMIN = {"Authorization": f"Bearer {SAT}", "New-Api-User": UID}

# 1. 创建 API Key 令牌,响应 data.key 即 sk-tg- 模型调用密钥
r = requests.post("http://localhost:8000/api/token/", headers=ADMIN,
                  json={"name": "my-key", "remain_quota": 500000}, timeout=30)
api_key = r.json()["data"]["key"]          # sk-tg-...
print("新密钥:", api_key)

# 2. 用新 Key 调用 LLM
r = requests.post("http://localhost:8000/v1/chat/completions",
                  headers={"Authorization": f"Bearer {api_key}"},
                  json={"model": "gpt-4o-mini",
                        "messages": [{"role": "user", "content": "你好"}]},
                  timeout=120)
print(r.json()["choices"][0]["message"]["content"])

# 3. 查询积分流水,核对本次扣费(此时流水已生成)
r = requests.get("http://localhost:8000/api/transaction/self",
                headers={"Authorization": f"Bearer {UTOKEN}"},
                params={"type": "consume", "page": 1, "page_size": 5}, timeout=30)
for tx in r.json()["data"]["items"]:
    print(tx["id"], tx["amount"], tx["balance_after"], tx["created_at"])

# 4. 查询余额确认
r = requests.get("http://localhost:8000/api/user/self", headers=ADMIN, timeout=30)
print("余额:", r.json()["data"]["balance"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var sat = "<tg-sat-系统访问令牌>";
var uid = "<你的用户ID>";

// 1. 创建 API Key 令牌,解析 data.key 得到 sk-tg- 密钥
var body = "{\"name\":\"my-key\",\"remain_quota\":500000}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/token/"))
    .header("Authorization", "Bearer " + sat)
    .header("New-Api-User", uid)
    .header("Content-Type", "application/json")
    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
var resp = http.send(req, java.net.http.HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());  // 取 data.key

// 2. 用返回的 sk-tg- Key 调用 LLM
//    header Authorization: Bearer <sk-tg-...>,POST /v1/chat/completions
// 3. 查询积分流水核对扣费(tg-usr- 令牌)
//    GET /api/transaction/self?type=consume&page=1
// 4. 查询余额(tg-sat- + New-Api-User)
//    GET /api/user/self —— 解析 data.balance
// Node.js 18+
const SAT = "<tg-sat-系统访问令牌>";
const UID = "<你的用户ID>";
const UTOKEN = "<tg-usr-用户令牌>";
const ADMIN = { Authorization: `Bearer ${SAT}`, "New-Api-User": UID };

// 1. 创建 API Key 令牌,响应 data.key 即 sk-tg- 模型调用密钥
let resp = await fetch("http://localhost:8000/api/token/", {
  method: "POST",
  headers: { ...ADMIN, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "my-key", remain_quota: 500000 }),
});
const api_key = (await resp.json()).data.key; // sk-tg-...
console.log("新密钥:", api_key);

// 2. 用新 Key 调用 LLM
resp = await fetch("http://localhost:8000/v1/chat/completions", {
  method: "POST",
  headers: { Authorization: `Bearer ${api_key}`, "Content-Type": "application/json" },
  body: JSON.stringify({ model: "gpt-4o-mini", messages: [{ role: "user", content: "你好" }] }),
});
console.log((await resp.json()).choices[0].message.content);

// 3. 查询积分流水,核对本次扣费(此时流水已生成)
resp = await fetch("http://localhost:8000/api/transaction/self?type=consume&page=1", {
  headers: { Authorization: `Bearer ${UTOKEN}` },
});
for (const tx of (await resp.json()).data.items)
  console.log(tx.id, tx.amount, tx.balance_after, tx.created_at);

// 4. 查询余额确认
resp = await fetch("http://localhost:8000/api/user/self", { headers: ADMIN });
console.log("余额:", (await resp.json()).data.balance);

工作流 2:图片异步生成——提交任务 → 轮询 → 下载(Python 示例)

顺序依赖:① 提交生图任务,从响应拿 uuid(原生生图模型)或 task_id(网关自动包装的任务);② 用它轮询对应查询接口,直到 status == 2(uuid 路径)或 status == "completed"(task_id 路径);③ 任务完成后才存在结果图 URL,此时下载。轮询建议间隔 5–10 秒,期间任务不会因不轮询而失败。

KEY=<sk-tg-你的密钥>

# 1. 提交文生图任务,记录返回的 uuid
curl http://localhost:8000/v1/cap/image/generate_image \
  -H "Authorization: Bearer $KEY" \
  -F "prompt=a cute cat on a windowsill" \
  -F "model=nano-banana-2-lite" \
  -F "aspect_ratio=1:1"
# 返回 {"uuid":"9f8e7d6c-xxxx"};若返回 {"task_id":"task-xxx"} 则第 2 步改查
# GET /v1/cap/tasks/{task_id},完成状态为 "completed",结果在 result_urls

# 2. 轮询任务(建议每 5–10 秒一次,status == 2 即完成)
curl http://localhost:8000/v1/cap/image/history/9f8e7d6c-xxxx \
  -H "Authorization: Bearer $KEY"

# 3. 下载结果图(URL 来自第 2 步响应)
curl -o result.jpg "<第2步响应中的结果图URL>"
import time
import requests

KEY = "<sk-tg-你的密钥>"
HDRS = {"Authorization": f"Bearer {KEY}"}

# 1. 提交文生图任务,拿 uuid
r = requests.post("http://localhost:8000/v1/cap/image/generate_image",
                  headers=HDRS,
                  data={"prompt": "a cute cat on a windowsill",
                        "model": "nano-banana-2-lite",
                        "aspect_ratio": "1:1"},
                  timeout=120)
data = r.json()
uuid = data.get("uuid")
task_id = data.get("task_id")   # 二选一:网关包装的任务走 /v1/cap/tasks
print("任务标识:", uuid or task_id)

# 2. 轮询直到完成(uuid 路径:status == 2)
while True:
    if uuid:
        r = requests.get(
            f"http://localhost:8000/v1/cap/image/history/{uuid}",
            headers=HDRS, timeout=30)
        st, result = r.json().get("status"), r.json()
        done = st == 2
    else:
        r = requests.get(
            f"http://localhost:8000/v1/cap/tasks/{task_id}",
            headers=HDRS, timeout=30)
        result = r.json()
        st, done = result.get("status"), result.get("status") == "completed"
    print("status:", st)
    if done:
        break
    time.sleep(5)

# 3. 取结果图 URL 并下载
url = result.get("image_url") or (result.get("result_urls") or [""])[0]
print("结果图:", url)
img = requests.get(url, timeout=120)
with open("result.jpg", "wb") as f:
    f.write(img.content)
// JDK 11+:提交 → 轮询 → 下载(核心骨架)
var http = java.net.http.HttpClient.newHttpClient();
var key = "<sk-tg-你的密钥>";

// 1. 提交任务(form-data 略,见图片章节 Java 示例),取响应中的 uuid
// 2. 每 5 秒轮询 GET /v1/cap/image/history/{uuid},
//    响应包含 "status": 2 即完成;task_id 路径改查 GET /v1/cap/tasks/{task_id},
//    "status": "completed" 且结果在 result_urls
// 3. 用结果 URL 下载:
var dl = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("<结果图URL>")).GET().build();
var bytes = http.send(dl, java.net.http.HttpResponse.BodyHandlers.ofByteArray());
java.nio.file.Files.write(java.nio.file.Path.of("result.jpg"), bytes.body());
// Node.js 18+
import { writeFile } from "node:fs/promises";

const KEY = "<sk-tg-你的密钥>";
const HDRS = { Authorization: `Bearer ${KEY}` };

// 1. 提交文生图任务,拿 uuid
const fd = new FormData();
fd.append("prompt", "a cute cat on a windowsill");
fd.append("model", "nano-banana-2-lite");
fd.append("aspect_ratio", "1:1");
let resp = await fetch("http://localhost:8000/v1/cap/image/generate_image", {
  method: "POST", headers: HDRS, body: fd,
});
const { uuid, task_id } = await resp.json();
console.log("任务标识:", uuid ?? task_id);

// 2. 轮询直到完成(uuid 路径:status === 2)
let result;
while (true) {
  const url = uuid
    ? `http://localhost:8000/v1/cap/image/history/${uuid}`
    : `http://localhost:8000/v1/cap/tasks/${task_id}`;
  resp = await fetch(url, { headers: HDRS });
  result = await resp.json();
  const st = result.status;
  console.log("status:", st);
  if (st === 2 || st === "completed") break;
  await new Promise((r) => setTimeout(r, 5000));
}

// 3. 取结果图 URL 并下载
const imageUrl = result.image_url ?? result.result_urls?.[0];
console.log("结果图:", imageUrl);
resp = await fetch(imageUrl);
await writeFile("result.jpg", Buffer.from(await resp.arrayBuffer()));

工作流 3:上传本地图片 → 图生图(文件接口 + 生图组合)

顺序依赖:① 先把本地图片上传到文件接口(purpose=relay),拿到 file_id;② 拼出公开访问 URL http://<服务器>/api/file/public/{file_id}(purpose=relay 的文件可通过该公开端点被上游拉取);③ 把这个 URL 作为 image 参数发起图生图。若直接用 multipart @本地文件 一步上传也可以(见图片章节),本工作流适用于「先存档再引用」或需要复用同一张图的场景。文件默认保留 2 天,过期后 URL 失效。

SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>; KEY=<sk-tg-你的密钥>

# 1. 上传本地图片(purpose=relay,返回 file_id)
curl http://localhost:8000/api/file/ \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -F "file=@photo.jpg" -F "purpose=relay"

# 2. 拼出公开 URL(purpose=relay 的文件无需鉴权即可访问)
#    http://localhost:8000/api/file/public/<file_id>

# 3. 用该 URL 发起图生图
curl http://localhost:8000/v1/cap/image/generate_image \
  -H "Authorization: Bearer $KEY" \
  -F "prompt=turn this into a watercolor painting" \
  -F "model=nano-banana-2-lite" \
  -F "image=http://localhost:8000/api/file/public/<file_id>"
import requests

SAT = "<tg-sat-系统访问令牌>"
UID = "<你的用户ID>"
KEY = "<sk-tg-你的密钥>"
ADMIN = {"Authorization": f"Bearer {SAT}", "New-Api-User": UID}

# 1. 上传本地图片(purpose=relay,返回 file_id)
with open("photo.jpg", "rb") as f:
    r = requests.post("http://localhost:8000/api/file/", headers=ADMIN,
                      files={"file": ("photo.jpg", f, "image/jpeg")},
                      data={"purpose": "relay"}, timeout=120)
file_id = r.json()["data"]["file_id"]
print("file_id:", file_id)

# 2. 拼出公开 URL(purpose=relay 的文件无需鉴权即可被上游拉取)
public_url = f"http://localhost:8000/api/file/public/{file_id}"

# 3. 用该 URL 发起图生图
r = requests.post("http://localhost:8000/v1/cap/image/generate_image",
                  headers={"Authorization": f"Bearer {KEY}"},
                  data={"prompt": "turn this into a watercolor painting",
                        "model": "nano-banana-2-lite",
                        "image": public_url},
                  timeout=120)
print(r.json())  # {"uuid": "..."} → 按工作流 2 轮询取结果
// JDK 11+(上传 multipart 细节见文件接口章节 Java 示例)
var http = java.net.http.HttpClient.newHttpClient();

// 1. POST /api/file/ 上传本地图片(purpose=relay),取 data.file_id
// 2. 公开 URL = http://localhost:8000/api/file/public/<file_id>
var publicUrl = "http://localhost:8000/api/file/public/<file_id>";

// 3. 用该 URL 发起图生图(form-data,字段 image = publicUrl),
//    响应 {"uuid": "..."} → 按工作流 2 轮询取结果
System.out.println("image=" + publicUrl);
// Node.js 18+
import { readFile } from "node:fs/promises";

const SAT = "<tg-sat-系统访问令牌>";
const UID = "<你的用户ID>";
const KEY = "<sk-tg-你的密钥>";
const ADMIN = { Authorization: `Bearer ${SAT}`, "New-Api-User": UID };

// 1. 上传本地图片(purpose=relay,返回 file_id)
const fd = new FormData();
fd.append("file", new Blob([await readFile("photo.jpg")]), "photo.jpg");
fd.append("purpose", "relay");
let resp = await fetch("http://localhost:8000/api/file/", {
  method: "POST", headers: ADMIN, body: fd,
});
const file_id = (await resp.json()).data.file_id;
console.log("file_id:", file_id);

// 2. 拼出公开 URL(purpose=relay 的文件无需鉴权即可被上游拉取)
const publicUrl = `http://localhost:8000/api/file/public/${file_id}`;

// 3. 用该 URL 发起图生图
const gen = new FormData();
gen.append("prompt", "turn this into a watercolor painting");
gen.append("model", "nano-banana-2-lite");
gen.append("image", publicUrl);
resp = await fetch("http://localhost:8000/v1/cap/image/generate_image", {
  method: "POST",
  headers: { Authorization: `Bearer ${KEY}` },
  body: gen,
});
console.log(await resp.json()); // {"uuid": "..."} → 按工作流 2 轮询取结果

NewAPI 兼容管理 API(tg-sat-)

平台管理面兼容 NewAPI(原 one-api)接口风格,NewAPI 生态客户端与脚本可直接对接。 认证同上(tg-sat- 令牌 + New-Api-User 头,或登录会话 Cookie); 所有响应为统一信封 {"success": true, "message": "", "data": ...}, 分页列表的 data 为 {"items": [...], "total": N, "page": 1, "page_size": 20}。

统一约定:下文示例中 SAT 为系统访问令牌(tg-sat-...)、UID 为你的用户 ID。使用 Bearer 令牌时必须同时携带 New-Api-User 头且与令牌归属用户一致,否则返回 401;浏览器 Cookie 会话则无需该头。标「管理员」的接口需要管理员权限,否则 403。

1. 系统状态(公开)

GET/api/status — 查询站点版本、系统名与本次启动时间。无需认证,常用于接入前的连通性探测(健康检查)。等价于 NewAPI 的同名端点。

返回 data 字段:version(后端版本号)、start_time(服务启动 Unix 秒级时间戳)、system_name(站点名,如「可信词元」)。

{
  "success": true, "message": "",
  "data": {"version": "1.0.0", "start_time": 1789722965, "system_name": "可信词元"}
}
curl http://localhost:8000/api/status
import requests
r = requests.get("http://localhost:8000/api/status", timeout=30)
print(r.json()["data"])  # {"version": "1.0.0", "start_time": ..., "system_name": ...}
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/status")).GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const r = await fetch("http://localhost:8000/api/status");
console.log((await r.json()).data);

2. 登录(建立会话 + 获取访问令牌)

POST/api/user/login — 用户名密码登录,一次请求完成两件事:① 设置 tg_session 会话 Cookie(HttpOnly,7 天滑动有效,供浏览器调用 /api/*);② 返回系统访问令牌 data.access_token(tg-sat- 前缀,首次登录自动生成),纯 API 客户端可直接用它 + New-Api-User 头访问管理接口。响应还带 data.user 完整用户对象,前端免二次请求。

参数类型必填说明
usernamestring是用户名
passwordstring是密码

返回 data 字段:access_token(系统访问令牌,即下文示例中的 SAT)、user(用户对象:字段同「3. 查询当前用户」)。失败时 HTTP 401(密码错误)或 403(账号停用)。

{
  "success": true, "message": "",
  "data": {
    "access_token": "tg-sat-xxxxxxxxxxxxxxxx",
    "user": {"id": 1, "username": "alice", "nickname": "Alice", "balance": 1000.0, "...": "..."}
  }
}
curl -X POST http://localhost:8000/api/user/login \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"你的密码"}'
import requests
r = requests.post("http://localhost:8000/api/user/login",
                 json={"username": "alice", "password": "你的密码"}, timeout=30)
data = r.json()["data"]
sat = data["access_token"]      # tg-sat-...,后续管理接口的 Bearer 令牌
uid = data["user"]["id"]        # 后续 New-Api-User 头的值
print(sat, uid)
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"username\":\"alice\",\"password\":\"你的密码\"}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/login"))
    .header("Content-Type", "application/json")
    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/login", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ username: "alice", password: "你的密码" }),
});
const { data } = await resp.json();
const sat = data.access_token; // tg-sat-...
const uid = data.user.id;      // New-Api-User 头的值

3. 查询当前用户(含余额)

GET/api/user/self — 查询当前认证用户的完整账户信息,是核对余额、确认令牌归属最常用的端点。可用 Cookie 会话或 tg-sat- + New-Api-User 调用。

返回 data 字段:

字段类型说明
id / username / nicknameint / string用户 ID(New-Api-User 头的值)、用户名、昵称
email / email_verified / wechat_boundstring / bool绑定邮箱、是否已验证、是否绑定微信
balancefloat积分余额(每次成功调用模型按实际用量扣减)
rate_multiplierfloat个人计费系数(已废弃:不参与计费,仅为兼容保留)
group_id / group_name / group_rateint / string / float所属用户分组与分组倍率(未分组时为 null,倍率按 1.0)
is_admin / has_admin_accessbool是否超级管理员 / 是否拥有后台权限
access_tokenstring当前系统访问令牌明文(可随时回看)
{
  "success": true, "message": "",
  "data": {"id": 1, "username": "alice", "nickname": "Alice",
           "balance": 1000.0,
           "group_id": 2, "group_name": "vip", "group_rate": 0.8, "...": "..."}
}
SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>
curl http://localhost:8000/api/user/self \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<你的用户ID>"
r = requests.get("http://localhost:8000/api/user/self",
                 headers={"Authorization": f"Bearer {SAT}",
                          "New-Api-User": UID}, timeout=30)
data = r.json()["data"]
print(data["balance"], data["group_rate"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/self"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/self", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<你的用户ID>",
  },
});
console.log((await resp.json()).data);

4. 修改昵称 / 密码

PUT/api/user/self — 修改当前用户资料。改昵称时昵称全局唯一(重复返回 409);改密码必须携带原密码 original_password(错误返回 401),改密成功后会向绑定邮箱发送通知。

参数类型必填说明
nicknamestring否新昵称(1–64 字符,全局唯一)
original_passwordstring改密时必填原密码
passwordstring否新密码(6–128 字符)

返回 data:修改后的完整用户对象(同「3. 查询当前用户」)。

SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>
curl -X PUT http://localhost:8000/api/user/self \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"nickname":"新昵称","original_password":"旧密码","password":"新密码"}'
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<你的用户ID>"
r = requests.put("http://localhost:8000/api/user/self",
                headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                json={"nickname": "新昵称"}, timeout=30)
print(r.json())
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"nickname\":\"新昵称\"}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/self"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>")
    .header("Content-Type", "application/json")
    .PUT(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/self", {
  method: "PUT",
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<你的用户ID>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ nickname: "新昵称" }),
});
console.log(await resp.json());

5. 我的可用模型

GET/api/user/models — 查询当前账号的模型池(可用模型名数组,NewAPI 形状),已按分组/授权范围过滤。可用于校验 sk-tg- Key 调用某模型前是否在可用范围内。

参数类型必填说明
detailint (query)否0(默认)返回模型名字符串数组;1 返回富对象数组(含 brands / manufacturers / is_open / available,供控制台配置用)
{
  "success": true, "message": "",
  "data": ["deepseek-v4-flash", "gpt-4o-mini", "glm-5.2"]
}
SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>
curl http://localhost:8000/api/user/models \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<你的用户ID>"
r = requests.get("http://localhost:8000/api/user/models",
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
print(r.json()["data"])  # ["deepseek-v4-flash", ...]
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/models"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/models", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<你的用户ID>",
  },
});
console.log((await resp.json()).data);

6. 重置系统访问令牌

GET/api/user/token — 生成新的系统访问令牌(tg-sat-),旧令牌立即失效。令牌泄露或定期轮换时使用。浏览器登录态调用即可(GET 操作但语义为重置)。

返回 data 字段:access_token(新令牌明文,请妥善保存,可在 /api/user/self 随时回看)。

{
  "success": true, "message": "",
  "data": {"access_token": "tg-sat-xxxxxxxxxxxxxxxx"}
}
SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>
curl http://localhost:8000/api/user/token \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<你的用户ID>"
r = requests.get("http://localhost:8000/api/user/token",
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
print(r.json()["data"]["access_token"])  # 新令牌(旧的已失效)
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/token"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/token", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<你的用户ID>",
  },
});
console.log((await resp.json()).data.access_token); // 新令牌

7. 令牌列表(我的 API Key)

GET/api/token/ — 分页查询当前用户的令牌列表。此处的「令牌」即模型调用密钥(ApiKey,sk-tg- 前缀)的 NewAPI 视图:可用于程序化盘点自己有哪些 Key、各自的剩余额度与累计消耗,而不必登录控制台。

参数类型必填说明
pint (query)否页码,默认 1
page_sizeint (query)否每页条数,默认 20,最大 100

返回 data.items 字段(NewAPI 令牌形状):id(令牌 ID)、name(名称)、key(密钥明文,sk-tg- 前缀)、status(1 启用 / 2 停用)、created_time / accessed_time(创建 / 最近使用 Unix 时间戳,无记录为 0)、expired_time(恒 -1,我方令牌不设过期时间)、remain_quota(剩余额度积分,不限额时为 0)、unlimited_quota(是否不限额度)、used_quota(按调用日志聚合的累计消耗积分)、model_limits_enabled(是否启用模型白名单)、model_limits(白名单模型名,逗号分隔字符串)。

{
  "success": true, "message": "",
  "data": {
    "items": [{
      "id": 3, "user_id": 1, "name": "prod-key", "key": "sk-tg-9f8e7d6c...",
      "status": 1, "created_time": 1789723000, "accessed_time": 1789800123,
      "expired_time": -1, "remain_quota": 500, "unlimited_quota": false,
      "used_quota": 128, "model_limits_enabled": true,
      "model_limits": "gpt-4o-mini,glm-5.2"
    }],
    "total": 1, "page": 1, "page_size": 20
  }
}
SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>
curl "http://localhost:8000/api/token/?p=1&page_size=10" \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<你的用户ID>"
r = requests.get("http://localhost:8000/api/token/",
                 params={"p": 1, "page_size": 10},
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
for t in r.json()["data"]["items"]:
    print(t["id"], t["name"], t["status"], t["remain_quota"], t["used_quota"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/token/?p=1&page_size=10"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/token/?p=1&page_size=10", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<你的用户ID>",
  },
});
for (const t of (await resp.json()).data.items) {
  console.log(t.id, t.name, t.status, t.remain_quota, t.used_quota);
}

8. 创建令牌(生成 sk-tg- 密钥)

POST/api/token/ — 创建一个新令牌,底层落库为模型调用密钥(ApiKey)。返回的 data.key 即完整的 sk-tg-... 密钥,可直接用于 /v1/* 模型调用——这是「先建 Key、再调用、再对账」组合流程的第一步(见组合调用示例 · 工作流 1)。可同时设置额度上限与模型白名单。

参数类型必填说明
namestring (body)否令牌名称,默认 default
remain_quotanumber (body)否额度上限(积分)。设置后该 Key 累计扣费达到上限即不可再调用
unlimited_quotabool (body)否true 不限额度(默认);false 且给了 remain_quota 时启用额度上限
model_limits_enabledbool (body)否是否启用模型白名单
model_limitsstring (body)否白名单模型名,逗号分隔(如 "gpt-4o-mini,glm-5.2");模型必须在你的可用模型池内,否则 400
expired_timenumber (body)否兼容字段:我方令牌不设过期,该值被忽略(返回恒 -1)

返回 data 字段:同「7. 令牌列表」的单个令牌对象;重点取 key(sk-tg- 明文)与 id(后续更新 / 删除用)。

{
  "success": true, "message": "",
  "data": {
    "id": 4, "user_id": 1, "name": "ci-pipeline", "key": "sk-tg-1a2b3c4d...",
    "status": 1, "created_time": 1789810000, "accessed_time": 0,
    "expired_time": -1, "remain_quota": 500, "unlimited_quota": false,
    "used_quota": 0, "model_limits_enabled": true,
    "model_limits": "gpt-4o-mini"
  }
}
SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>
curl -X POST http://localhost:8000/api/token/ \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"name":"ci-pipeline","remain_quota":500,"unlimited_quota":false,"model_limits_enabled":true,"model_limits":"gpt-4o-mini"}'
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<你的用户ID>"
r = requests.post("http://localhost:8000/api/token/",
                  headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                  json={"name": "ci-pipeline", "remain_quota": 500,
                        "unlimited_quota": False,
                        "model_limits_enabled": True,
                        "model_limits": "gpt-4o-mini"},
                  timeout=30)
key = r.json()["data"]["key"]  # sk-tg-...,可直接用于 /v1/* 调用
print(key)
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"name\":\"ci-pipeline\",\"remain_quota\":500,"
         + "\"unlimited_quota\":false,\"model_limits_enabled\":true,"
         + "\"model_limits\":\"gpt-4o-mini\"}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/token/"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>")
    .header("Content-Type", "application/json")
    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/token/", {
  method: "POST",
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<你的用户ID>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "ci-pipeline",
    remain_quota: 500,
    unlimited_quota: false,
    model_limits_enabled: true,
    model_limits: "gpt-4o-mini",
  }),
});
const key = (await resp.json()).data.key; // sk-tg-...
console.log(key);

9. 更新令牌 / 批量启停

PUT/api/token/ — 两种用法:单个更新(body 含 id,可改名称、额度与模型白名单)与批量启停(body 为 {"ids": [...], "enable": true/false})。额度语义:给 unlimited_quota: true(或不给 remain_quota)即清除上限,恢复不限额;remain_quota 为全量重设(非增量),如想把 500 剩 100 的 Key 提到 800,应直接传 800。

参数类型必填说明
idint (body)是*单个更新时的令牌 ID(*批量启停模式改用 ids)
namestring (body)否新名称(空串忽略)
remain_quotanumber (body)否新的额度上限(全量重设)
unlimited_quotabool (body)否true 清除额度上限
model_limits_enabled + model_limitsbool / string (body)否重设模型白名单;enabled=false 时清除白名单
idsint[] (body)是*批量启停模式:令牌 ID 数组,配合 enable
enablebool (body)否true 启用(status=1)/ false 停用(status=2)

返回 data 字段:单个更新返回更新后的令牌对象(形状同小节 7);批量启停返回 {"ok": true, "modified": N}(N 为实际改动的条数,仅本人令牌会被改动)。

{
  "success": true, "message": "",
  "data": {"ok": true, "modified": 3}
}
SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>
# 单个更新:把 4 号令牌额度重设为 800
curl -X PUT http://localhost:8000/api/token/ \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"id":4,"remain_quota":800}'
# 批量停用 4、5、6 号令牌
curl -X PUT http://localhost:8000/api/token/ \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"ids":[4,5,6],"enable":false}'
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<你的用户ID>"
H = {"Authorization": f"Bearer {SAT}", "New-Api-User": UID}
# 单个更新
r1 = requests.put("http://localhost:8000/api/token/", headers=H,
                  json={"id": 4, "remain_quota": 800}, timeout=30)
print(r1.json()["data"]["remain_quota"])  # 800
# 批量停用
r2 = requests.put("http://localhost:8000/api/token/", headers=H,
                  json={"ids": [4, 5, 6], "enable": False}, timeout=30)
print(r2.json()["data"])  # {"ok": true, "modified": 3}
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"id\":4,\"remain_quota\":800}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/token/"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>")
    .header("Content-Type", "application/json")
    .PUT(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
// 单个更新
let resp = await fetch("http://localhost:8000/api/token/", {
  method: "PUT",
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<你的用户ID>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 4, remain_quota: 800 }),
});
console.log((await resp.json()).data.remain_quota); // 800
// 批量停用
resp = await fetch("http://localhost:8000/api/token/", {
  method: "PUT",
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<你的用户ID>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ ids: [4, 5, 6], enable: false }),
});
console.log((await resp.json()).data); // { ok: true, modified: 3 }

10. 查询 / 删除 / 搜索令牌

同一资源的其余四个操作:

  • GET/api/token/{id} — 按 ID 查询单个令牌(形状同小节 7 的 item;非本人令牌返回 404)
  • DELETE/api/token/{id} — 物理删除令牌(调用日志仅存 ID 引用,历史记录不受影响);返回 {"ok": true}
  • POST/api/token/batch — 批量删除,body 为 {"ids": [1, 2, 3]};不存在的 ID 静默跳过,返回 {"ok": true, "deleted": N}(N 为实际删除数)
  • GET/api/token/search?keyword=xx — 按名称关键字搜索(包含匹配),返回裸数组(无分页信封),最多 100 条
{
  "success": true, "message": "",
  "data": {"ok": true, "deleted": 2}
}
SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>
# 查询单个
curl http://localhost:8000/api/token/4 -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
# 删除单个
curl -X DELETE http://localhost:8000/api/token/4 -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
# 批量删除
curl -X POST http://localhost:8000/api/token/batch \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" -d '{"ids":[5,6]}'
# 按名称搜索
curl "http://localhost:8000/api/token/search?keyword=ci" \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<你的用户ID>"
H = {"Authorization": f"Bearer {SAT}", "New-Api-User": UID}
base = "http://localhost:8000/api/token"
# 查询单个
print(requests.get(f"{base}/4", headers=H, timeout=30).json()["data"]["name"])
# 批量删除
r = requests.post(f"{base}/batch", headers=H,
                  json={"ids": [5, 6]}, timeout=30)
print(r.json()["data"])  # {"ok": true, "deleted": 2}
# 按名称搜索
r = requests.get(f"{base}/search", headers=H,
                 params={"keyword": "ci"}, timeout=30)
print([t["name"] for t in r.json()["data"]])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
// 查询单个
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/token/4"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
// 批量删除
var body = "{\"ids\":[5,6]}";
var req2 = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/token/batch"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>")
    .header("Content-Type", "application/json")
    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req2,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const H = {
  Authorization: "Bearer <tg-sat-系统访问令牌>",
  "New-Api-User": "<你的用户ID>",
};
const base = "http://localhost:8000/api/token";
// 查询单个
let resp = await fetch(`${base}/4`, { headers: H });
console.log((await resp.json()).data.name);
// 批量删除
resp = await fetch(`${base}/batch`, {
  method: "POST",
  headers: { ...H, "Content-Type": "application/json" },
  body: JSON.stringify({ ids: [5, 6] }),
});
console.log((await resp.json()).data); // { ok: true, deleted: 2 }
// 按名称搜索
resp = await fetch(`${base}/search?keyword=ci`, { headers: H });
console.log((await resp.json()).data.map((t) => t.name));

11. 我的调用日志

GET/api/log/self — 分页查询自己的模型调用日志(含计费明细),是「调用后对账」的核心端点(见组合调用示例 · 工作流 1)。每一行对应一次 /v1/* 请求,包含该次的实际扣费、倍率与延迟。配套 GET/api/log/self/search?keyword=xx 按模型名 / 路径 / request_id / 错误信息做关键字搜索(返回裸数组,最多 100 条)。

参数类型必填说明
categorystring (query)否按类别过滤:llm / image / video / speech
pint (query)否页码,默认 1
page_sizeint (query)否每页条数,默认 20,最大 100

返回 data.items 字段:id、request_id(本次调用的请求 ID,与响应头一致,可作对账主键)、category / model / path(类别 / 模型 / 路径)、http_status、stream(是否流式)、prompt_tokens / completion_tokens(输入 / 输出 token 数)、credits_charged(本次实际扣费积分)、rate_multiplier / sale_coefficient(计费倍率 / 售价系数)、upstream_cost / upstream_cost_cny / exchange_rate(上游成本美元 / 人民币 / 汇率)、latency_ms / ttft_ms(总延迟 / 首字延迟)、error(错误信息,成功为 null)、created_at。

{
  "success": true, "message": "",
  "data": {
    "items": [{
      "id": 9012, "user_id": 1, "request_id": "req-7f2c...", "category": "llm",
      "model": "gpt-4o-mini", "path": "/v1/chat/completions",
      "http_status": 200, "stream": false,
      "prompt_tokens": 128, "completion_tokens": 256,
      "upstream_cost": 0.000192, "upstream_cost_cny": 0.00138, "exchange_rate": 7.2,
      "rate_multiplier": 1.0, "sale_coefficient": 1.5,
      "credits_charged": 0.33, "latency_ms": 842, "ttft_ms": 311,
      "error": null, "created_at": "2026-09-18T10:24:31"
    }],
    "total": 57, "page": 1, "page_size": 20
  }
}
SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>
curl "http://localhost:8000/api/log/self?category=llm&p=1&page_size=10" \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
# 关键字搜索(按模型名 / request_id / 错误信息)
curl "http://localhost:8000/api/log/self/search?keyword=gpt-4o" \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<你的用户ID>"
r = requests.get("http://localhost:8000/api/log/self",
                 params={"category": "llm", "p": 1, "page_size": 10},
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
for log in r.json()["data"]["items"]:
    print(log["created_at"], log["model"], log["credits_charged"],
          log["latency_ms"], log["request_id"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/log/self?category=llm&p=1&page_size=10"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch(
  "http://localhost:8000/api/log/self?category=llm&p=1&page_size=10",
  {
    headers: {
      Authorization: "Bearer <tg-sat-系统访问令牌>",
      "New-Api-User": "<你的用户ID>",
    },
  },
);
for (const log of (await resp.json()).data.items) {
  console.log(log.created_at, log.model, log.credits_charged, log.request_id);
}

12. 我的用量统计

GET/api/log/self/stat — 查询自己的汇总用量,三个数字即可覆盖常见监控场景:累计扣费、最近 1 分钟请求速率、最近 1 分钟 token 速率。适合做轻量巡检(如每分钟采一次写入监控面板,无需拉全量日志)。

返回 data 字段:quota(历史累计扣费积分,float)、rpm(Requests Per Minute,近 1 分钟请求数)、tpm(Tokens Per Minute,近 1 分钟输入+输出 token 总数)。

{
  "success": true, "message": "",
  "data": {"quota": 128.5, "rpm": 3, "tpm": 2140}
}
SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>
curl http://localhost:8000/api/log/self/stat \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<你的用户ID>"
r = requests.get("http://localhost:8000/api/log/self/stat",
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
print(r.json()["data"])  # {"quota": 128.5, "rpm": 3, "tpm": 2140}
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/log/self/stat"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/log/self/stat", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<你的用户ID>",
  },
});
console.log((await resp.json()).data); // { quota: 128.5, rpm: 3, tpm: 2140 }

13. 我的看板数据

GET/api/data/self — 个人看板三要素:当前余额、累计消费、可用模型数。打开控制台首页看到的数字就是它;程序化场景适合做「启动时先检查余额是否充足」的前置判断。

返回 data 字段:model_count(当前可用模型数量)、quota(账户余额,积分,float)、used_quota(历史累计消费积分,float)。

{
  "success": true, "message": "",
  "data": {"model_count": 46, "quota": 1000.0, "used_quota": 128.5}
}
SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>
curl http://localhost:8000/api/data/self \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<你的用户ID>"
r = requests.get("http://localhost:8000/api/data/self",
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
data = r.json()["data"]
if data["quota"] < 10:   # 余额不足预警
    print("余额不足:", data["quota"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/data/self"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/data/self", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<你的用户ID>",
  },
});
const data = (await resp.json()).data;
if (data.quota < 10) console.log("余额不足:", data.quota);

14. 全站调用日志(管理员)

GET/api/log/ — 管理员查询全站调用日志,支持按用户、类别、供应商、HTTP 状态与日期范围过滤。配套 GET/api/log/search?keyword=xx(关键字搜索,裸数组最多 100 条)与 GET/api/log/stat(全站汇总:quota 累计扣费 / rpm / tpm 近 1 分钟速率)。需要 admin:calls:view 权限。

参数类型必填说明
user_idint (query)否按用户过滤
categorystring (query)否llm / image / video / speech
provider_idint (query)否按上游供应商过滤
http_status / http_status_groupint / string (query)否按状态码 / 状态组(如 4xx、5xx)过滤
start_date / end_datedate (query)否日期范围,格式 YYYY-MM-DD
page / page_sizeint (query)否分页,默认 1 / 20(page_size 最大 100)

返回 data.items 字段:同「11. 我的调用日志」(CallLogOut 形状,含全站视角的 user_id)。

{
  "success": true, "message": "",
  "data": {
    "items": [{
      "id": 9013, "user_id": 42, "request_id": "req-a31f...", "category": "image",
      "model": "seedream-4.0", "path": "/v1/cap/image/generate_image",
      "http_status": 200, "stream": false, "prompt_tokens": 0, "completion_tokens": 0,
      "upstream_cost": 0.03, "upstream_cost_cny": 0.216, "exchange_rate": 7.2,
      "rate_multiplier": 1.0, "sale_coefficient": 1.4,
      "credits_charged": 4.2, "latency_ms": 5230, "ttft_ms": null,
      "error": null, "created_at": "2026-09-18T10:31:07"
    }],
    "total": 1532, "page": 1, "page_size": 20
  }
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
# 查今天起、5xx 错误的全站日志
curl "http://localhost:8000/api/log/?http_status_group=5xx&start_date=2026-09-18&page=1" \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
# 全站汇总
curl http://localhost:8000/api/log/stat \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.get("http://localhost:8000/api/log/",
                 params={"user_id": 42, "category": "llm",
                         "start_date": "2026-09-18", "page": 1},
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
data = r.json()["data"]
print("total:", data["total"])
for log in data["items"]:
    print(log["created_at"], log["user_id"], log["model"], log["credits_charged"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/log/?user_id=42&category=llm&page=1"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch(
  "http://localhost:8000/api/log/?user_id=42&category=llm&page=1",
  {
    headers: {
      Authorization: "Bearer <tg-sat-系统访问令牌>",
      "New-Api-User": "<管理员用户ID>",
    },
  },
);
const data = (await resp.json()).data;
console.log("total:", data.total);
for (const log of data.items) {
  console.log(log.created_at, log.user_id, log.model, log.credits_charged);
}

15. 全站看板 / 用户分组(管理员)

GET/api/data/ — 管理员看板数据(需要 admin:stats:view 权限):注册用户数、总调用量、累计扣费 / 授予积分、今日调用数与活跃供应商数。GET/api/group/ — 用户分组列表(NewAPI 形状:symbol = 分组名、ratio = 分组倍率),需 admin:groups:view 或 admin:users:edit 权限。

/api/data/ 返回 data 字段:total_users(用户数)、total_calls(总调用次数)、total_credits_consumed / total_credits_granted(累计消费 / 授予积分)、calls_today(今日调用数)、active_providers(活跃供应商数)。

{
  "success": true, "message": "",
  "data": {"total_users": 128, "total_calls": 45678, "total_credits_consumed": 9123.4,
           "total_credits_granted": 50000.0, "calls_today": 356, "active_providers": 12}
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl http://localhost:8000/api/data/ -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
# 用户分组列表(symbol=分组名,ratio=倍率)
curl http://localhost:8000/api/group/ -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
H = {"Authorization": f"Bearer {SAT}", "New-Api-User": UID}
stats = requests.get("http://localhost:8000/api/data/", headers=H,
                     timeout=30).json()["data"]
print(stats["calls_today"], "calls today,", stats["active_providers"], "providers")
groups = requests.get("http://localhost:8000/api/group/", headers=H,
                      timeout=30).json()["data"]
for g in groups:
    print(g["id"], g["symbol"], "x", g["ratio"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/data/"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const H = {
  Authorization: "Bearer <tg-sat-系统访问令牌>",
  "New-Api-User": "<管理员用户ID>",
};
const stats = (await (await fetch("http://localhost:8000/api/data/", { headers: H })).json()).data;
console.log(stats.calls_today, "calls today,", stats.active_providers, "providers");
const groups = (await (await fetch("http://localhost:8000/api/group/", { headers: H })).json()).data;
for (const g of groups) console.log(g.id, g.symbol, "x", g.ratio);

16. 配置项读写(管理员)

GET/api/option/ — 读取全站配置项(键值对数组,需要 admin:stats:view 或 admin:config:edit 权限;敏感项如 SMTP 密码不返回)。PUT/api/option/ — 批量更新配置(需要 admin:config:edit 权限):body 兼容单对象 {"key": "...", "value": "..."} 与数组 [{"key": "...", "value": "..."}] 两种形式,返回更新后的键值对(含 updated_at)。

参数类型必填说明
keystring (body)是配置键,如 usd_to_cny(美元汇率)、rate_limit_per_key(每 Key 秒级限流,0 = 不限)
valuestring (body)是配置值(一律为字符串形式,数字也转字符串)
{
  "success": true, "message": "",
  "data": [
    {"key": "usd_to_cny", "value": "7.2"},
    {"key": "rate_limit_per_key", "value": "200"}
  ]
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
# 读取全部配置
curl http://localhost:8000/api/option/ -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
# 更新汇率(单对象形式)
curl -X PUT http://localhost:8000/api/option/ \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"key":"usd_to_cny","value":"7.1"}'
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
H = {"Authorization": f"Bearer {SAT}", "New-Api-User": UID}
opts = requests.get("http://localhost:8000/api/option/", headers=H,
                    timeout=30).json()["data"]
print({o["key"]: o["value"] for o in opts})
r = requests.put("http://localhost:8000/api/option/", headers=H,
                 json=[{"key": "usd_to_cny", "value": "7.1"}],  # 数组批量形式
                 timeout=30)
print(r.json()["data"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"key\":\"usd_to_cny\",\"value\":\"7.1\"}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/option/"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>")
    .header("Content-Type", "application/json")
    .PUT(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const H = {
  Authorization: "Bearer <tg-sat-系统访问令牌>",
  "New-Api-User": "<管理员用户ID>",
  "Content-Type": "application/json",
};
// 更新配置
let resp = await fetch("http://localhost:8000/api/option/", {
  method: "PUT",
  headers: H,
  body: JSON.stringify({ key: "usd_to_cny", value: "7.1" }),
});
console.log((await resp.json()).data);
// 读取全部
resp = await fetch("http://localhost:8000/api/option/", { headers: H });
const opts = (await resp.json()).data;
console.log(Object.fromEntries(opts.map((o) => [o.key, o.value])));

17. 渠道列表与详情(管理员)

GET/api/channel/ — 分页查询渠道列表。「渠道」在本平台等价于上游供应商(一个 NewAPI 渠道 = 一个 OpenAI 兼容上游 + 一组密钥)。GET/api/channel/{id} — 单个渠道详情(含明文密钥);GET/api/channel/search?keyword=xx — 按名称 / base_url / 模型名搜索(裸数组最多 100 条)。列表需要 admin:providers:view 权限。

参数类型必填说明
p / page_sizeint (query)否分页,默认 1 / 20(page_size 最大 100)
statusint (query)否1 启用 / 2 禁用
namestring (query)否按渠道名称包含过滤

返回 data.items 字段(NewAPI Channel 形状):id、type(恒 1,统一为 OpenAI 兼容类型)、status(1 启用 / 2 禁用)、name、weight / priority(调度权重)、base_url(上游地址)、key(密钥,多行合并:首行 = 供应商主密钥,其余行 = 子密钥)、models(该渠道的活跃模型名,逗号分隔)、group(恒 default)、balance / balance_updated_time(上游余额与抓取时间)、created_time;used_quota / model_mapping 等为兼容占位(用量分析走原生接口)。

{
  "success": true, "message": "",
  "data": {
    "items": [{
      "id": 1, "type": 1, "status": 1, "name": "openai-relay", "weight": 100,
      "created_time": 1789600000, "test_time": 0, "response_time": 0,
      "base_url": "https://api.upstream.example/v1",
      "key": "sk-up-main-xxx\nsk-up-sub-yyy",
      "models": "gpt-4o,gpt-4o-mini", "group": "default",
      "used_quota": 0, "model_mapping": "", "priority": 100,
      "auto_ban": 1, "other_info": "", "balance": 96.5,
      "balance_updated_time": 1789790000, "tag": "", "setting": "",
      "param_override": ""
    }],
    "total": 12, "page": 1, "page_size": 20
  }
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl "http://localhost:8000/api/channel/?p=1&page_size=10" \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
# 单个渠道详情
curl http://localhost:8000/api/channel/1 \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.get("http://localhost:8000/api/channel/",
                 params={"p": 1, "page_size": 10, "status": 1},
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
for ch in r.json()["data"]["items"]:
    print(ch["id"], ch["name"], ch["base_url"],
          ch["models"].split(","), "balance:", ch["balance"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/channel/?p=1&page_size=10"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/channel/?p=1&page_size=10", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
  },
});
for (const ch of (await resp.json()).data.items) {
  console.log(ch.id, ch.name, ch.models.split(","), "balance:", ch.balance);
}

18. 创建渠道(管理员)

POST/api/channel/ — 创建渠道(供应商),需要 admin:providers:manage 权限。支持 NewAPI 三种模式:single(默认,单渠道单密钥)、batch(key 多行时每行各建一个同名供应商)、multi_to_single(key 多行合并为一个供应商:首行主密钥,其余行入子密钥池)。models 会同步创建目录条目与基础路由(模型随即进入调度池)。另可用 POST/api/channel/fetch_models(body:{"base_url": "...", "key": "..."})先探测上游模型列表,不落库。

参数类型必填说明
namestring (body)是渠道名称
base_urlstring (body)是上游地址(OpenAI 兼容)
keystring (body)是上游密钥;多行(\n 分隔)时按 mode 解释
modestring (body)否single(默认)/ batch / multi_to_single
modelsstring / string[] (body)否模型名列表,逗号分隔字符串或数组
statusint (body)否1 启用(默认)/ 2 禁用
priority / weightint (body)否调度权重,默认 0
categorystring (body)否目录条目的默认类别,默认 llm

返回 data 字段:单个渠道对象(形状同小节 17);batch 模式返回数组。

SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl -X POST http://localhost:8000/api/channel/ \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"name":"my-openai","base_url":"https://api.upstream.example/v1","key":"sk-up-xxx","models":"gpt-4o,gpt-4o-mini","priority":100}'
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.post("http://localhost:8000/api/channel/",
                  headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                  json={"name": "my-openai",
                        "base_url": "https://api.upstream.example/v1",
                        "key": "sk-up-xxx",
                        "models": "gpt-4o,gpt-4o-mini",
                        "priority": 100},
                  timeout=30)
print(r.json()["data"]["id"])  # 新渠道 ID
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"name\":\"my-openai\",\"base_url\":\"https://api.upstream.example/v1\","
         + "\"key\":\"sk-up-xxx\",\"models\":\"gpt-4o,gpt-4o-mini\",\"priority\":100}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/channel/"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>")
    .header("Content-Type", "application/json")
    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/channel/", {
  method: "POST",
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "my-openai",
    base_url: "https://api.upstream.example/v1",
    key: "sk-up-xxx",
    models: "gpt-4o,gpt-4o-mini",
    priority: 100,
  }),
});
console.log((await resp.json()).data.id); // 新渠道 ID

19. 更新渠道 / 子密钥管理(管理员)

PUT/api/channel/ — 更新渠道(body 必含 id,需要 admin:providers:manage 权限)。key 多行 = 全量重写密钥(首行替换主密钥,其余行整体替换子密钥池);models = 全量同步目录,不在列表中的旧条目将被停用。model_mapping / tag / setting 等我方无对应概念的字段接受但忽略。配套 POST/api/channel/multi_key/manage — 管理单个子密钥:body 为 {"channel_id": 1, "key_id": 3, "action": "enable|disable|delete"}。

参数类型必填说明
idint (body)是渠道 ID
name / base_urlstring (body)否新名称 / 新地址(空串忽略)
keystring (body)否多行密钥全量重写(首行主密钥)
modelsstring / string[] (body)否全量同步模型目录(列表外停用)
statusint (body)否1 启用 / 2 禁用
priority / weightint (body)否调度权重

返回 data 字段:更新后的渠道对象(形状同小节 17);子密钥管理返回 {"ok": true, "action": "...", "key_id": N}。

SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
# 改名 + 重写密钥(一行主密钥)+ 全量同步模型
curl -X PUT http://localhost:8000/api/channel/ \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"id":1,"name":"openai-relay-v2","key":"sk-up-newkey","models":"gpt-4o,gpt-4o-mini,deepseek-v4-flash"}'
# 停用 1 号渠道的 3 号子密钥
curl -X POST http://localhost:8000/api/channel/multi_key/manage \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"channel_id":1,"key_id":3,"action":"disable"}'
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.put("http://localhost:8000/api/channel/",
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 json={"id": 1, "status": 2},  # 禁用渠道
                 timeout=30)
print(r.json()["data"]["status"])  # 2
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"id\":1,\"status\":2}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/channel/"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>")
    .header("Content-Type", "application/json")
    .PUT(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/channel/", {
  method: "PUT",
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 1, status: 2 }), // 禁用渠道
});
console.log((await resp.json()).data.status); // 2

20. 渠道测试 / 余额刷新 / 删除(管理员)

渠道运维三件套:

  • GET/api/channel/test/{id} — 测试单个渠道:探测该供应商首个活跃路由(一次真实上游请求,同步返回);无路由时回退为上游 /v1/models 连通性探测。返回 {"ok", "channel_id", "available", "error", "time"(毫秒), "mode"(route_probe / upstream_probe)}。不带 ID 的 GET /api/channel/test 触发全量后台健康检测。
  • GET/api/channel/update_balance — 刷新全部供应商的上游余额(同步执行);GET /api/channel/update_balance/{id} — 刷新单个。余额低于告警线时自动触发通知。
  • DELETE/api/channel/{id} — 删除渠道(级联清理该供应商的路由 / 目录条目 / 子密钥),返回 {"ok": true}。测试需要 admin:models:manage,余额 / 删除需要 admin:providers:view / admin:providers:manage。
{
  "success": true, "message": "",
  "data": {"ok": true, "channel_id": 1, "available": true,
           "error": null, "time": 643, "mode": "route_probe"}
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
# 测试 1 号渠道
curl http://localhost:8000/api/channel/test/1 \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
# 刷新 1 号渠道余额
curl http://localhost:8000/api/channel/update_balance/1 \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
# 删除 1 号渠道
curl -X DELETE http://localhost:8000/api/channel/1 \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
H = {"Authorization": f"Bearer {SAT}", "New-Api-User": UID}
# 测试
r = requests.get("http://localhost:8000/api/channel/test/1", headers=H, timeout=60)
d = r.json()["data"]
print("available:", d["available"], "time:", d["time"], "ms")
# 刷新余额
r = requests.get("http://localhost:8000/api/channel/update_balance/1",
                 headers=H, timeout=60)
print(r.json()["data"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/channel/test/1"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const H = {
  Authorization: "Bearer <tg-sat-系统访问令牌>",
  "New-Api-User": "<管理员用户ID>",
};
// 测试
let resp = await fetch("http://localhost:8000/api/channel/test/1", { headers: H });
let d = (await resp.json()).data;
console.log("available:", d.available, "time:", d.time, "ms");
// 删除
resp = await fetch("http://localhost:8000/api/channel/1", { method: "DELETE", headers: H });
console.log((await resp.json()).data); // { ok: true }

21. 模型元数据列表与详情(管理员)

GET/api/models/ — 分页查询模型目录(元数据管理视角,需要 admin:models:view 权限)。注意与无尾斜杠的 GET/api/models 区分:后者返回当前登录用户的可用模型名数组(与 /api/user/models 同源)。配套:GET/api/models/search?keyword=xx(搜索,裸数组)、GET/api/models/missing(缺成本报价的模型清单——这些模型无法精确计费,放量前需补价)。

参数类型必填说明
p / page_sizeint (query)否分页,默认 1 / 20(page_size 最大 100)
keywordstring (query)否按模型名 / 对外名 / 说明搜索

返回 data.items 字段:id(目录条目 ID)、model_name(引入模型名)、description(模型说明)、vendor_id(恒 1,统一品牌「可信词元」)、status(1 启用 / 2 禁用)、sort_order;我方扩展字段:provider_id(所属供应商)、public_name(对外显示名)、category(类别数组)、is_public(是否公开)、context_length(上下文长度)。

{
  "success": true, "message": "",
  "data": {
    "items": [{
      "id": 12, "model_name": "gpt-4o-mini", "description": "OpenAI 快速对话模型",
      "vendor_id": 1, "status": 1, "sort_order": 12, "provider_id": 1,
      "public_name": "gpt-4o-mini", "category": ["llm"],
      "is_public": true, "context_length": 128000
    }],
    "total": 46, "page": 1, "page_size": 20
  }
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
# 元数据列表(注意尾斜杠)
curl "http://localhost:8000/api/models/?p=1&page_size=10" \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
# 单个模型(按目录条目 id)
curl http://localhost:8000/api/models/12 \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
# 缺价模型清单
curl http://localhost:8000/api/models/missing \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.get("http://localhost:8000/api/models/",
                 params={"keyword": "gpt", "page_size": 10},
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
for m in r.json()["data"]["items"]:
    print(m["id"], m["model_name"], m["status"], m["category"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/models/?p=1&page_size=10"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/models/?p=1&page_size=10", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
  },
});
for (const m of (await resp.json()).data.items) {
  console.log(m.id, m.model_name, m.status, m.category);
}

22. 更新模型元数据(管理员)

PUT/api/models/ — 更新目录条目的元数据(body 含 id,需要 admin:models:manage 权限)。字段映射:model_name → 引入模型名、description → 模型说明、status(1/2)→ 启停。传 "status_only": true 时只更新状态,其余字段忽略——只做启停操作的客户端建议始终带上它,防止误清空说明文字。

参数类型必填说明
idint (body)是目录条目 ID
statusint (body)否1 启用 / 2 禁用
model_namestring (body)否新模型名(status_only 时忽略)
descriptionstring (body)否新说明(status_only 时忽略)
status_onlybool (body)否true 仅更新状态(推荐用于纯启停)

返回 data 字段:{"ok": true, "id": N, "model_name": "...", "status": "active|disabled"}。

{
  "success": true, "message": "",
  "data": {"ok": true, "id": 12, "model_name": "gpt-4o-mini", "status": "active"}
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
# 仅停用 12 号模型
curl -X PUT http://localhost:8000/api/models/ \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"id":12,"status":2,"status_only":true}'
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.put("http://localhost:8000/api/models/",
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 json={"id": 12, "status": 2, "status_only": True},
                 timeout=30)
print(r.json()["data"])  # {"ok": true, "id": 12, ...}
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"id\":12,\"status\":2,\"status_only\":true}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/models/"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>")
    .header("Content-Type", "application/json")
    .PUT(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/models/", {
  method: "PUT",
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 12, status: 2, status_only: true }),
});
console.log((await resp.json()).data); // { ok: true, id: 12, ... }

23. 价格目录与供应商(公开)

GET/api/pricing — 面向下游软件的价格目录:各对外模型的售价(积分计),可带 ?category=llm,image 过滤(多类别取交集)。GET/api/vendors — 供应商列表:本平台对用户侧统一展示为单一品牌「可信词元」,恒返回 [{"id": 1, "name": "可信词元", ...}],不接受创建 / 修改 / 删除。两个接口均无需管理员权限。

/api/pricing 返回 data 字段(数组):id(对外模型名)、display_name(显示名)、category(类别)、input_price / output_price(输入 / 输出单价)、currency(恒 credits)、unit(计价单位)、available(当前是否可用)。

{
  "success": true, "message": "",
  "data": [
    {"id": "gpt-4o-mini", "display_name": "gpt-4o-mini", "category": "llm",
     "input_price": 0.0012, "output_price": 0.0048,
     "currency": "credits", "unit": "per 1k tokens", "available": true}
  ]
}
curl "http://localhost:8000/api/pricing?category=llm"
curl http://localhost:8000/api/vendors
import requests
prices = requests.get("http://localhost:8000/api/pricing",
                      params={"category": "llm"}, timeout=30).json()["data"]
for p in prices:
    print(p["id"], "in:", p["input_price"], "out:", p["output_price"])
vendors = requests.get("http://localhost:8000/api/vendors", timeout=30).json()["data"]
print(vendors)  # [{"id": 1, "name": "可信词元", ...}]
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/pricing?category=llm")).GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const prices = (await (await fetch("http://localhost:8000/api/pricing?category=llm")).json()).data;
for (const p of prices) console.log(p.id, "in:", p.input_price, "out:", p.output_price);
const vendors = (await (await fetch("http://localhost:8000/api/vendors")).json()).data;
console.log(vendors); // [{ id: 1, name: "可信词元", ... }]

24. 扩展域(NewAPI 风格命名)

除上述对齐端点外,原生管理能力也按 NewAPI 风格路径提供(鉴权与信封约定一致,权限要求与原生管理后台相同)。常用扩展域:

  • /api/role/、/api/permission/self — 角色与权限管理
  • /api/transaction/ — 积分流水的管理视角(管理员用 my_auth("user:finance:read") 鉴权)
  • /api/model_route/、/api/model_proxy/ — 模型路由与代理配置
  • /api/key/ — Key 级配置(如模型使用时段)
  • /api/exchange_rate/、/api/bill_reconcile/ — 汇率与账单对账

完整清单见《运维操作手册》。扩展域接口形状遵循同一信封约定,此处不再逐一展开。

用户认证补充(公开 / 登录态)

25. 注册(需邮箱验证码)

POST/api/user/register — 公开注册端点。与 NewAPI 的差异:email 与 email_code 均为必填(防垃圾注册),请先调用「28. 发送邮箱验证码」获取验证码。注册成功返回 {"ok": true}(不签发会话),随后调用 /api/user/login 登录。用户名已被注册返回 409;绑定代理商的专属皮肤域名禁止自助注册(403)。

参数类型必填说明
usernamestring是用户名(3–64 字符)
passwordstring是密码(6–128 字符)
emailstring是邮箱(注册即视为已验证)
email_codestring是邮箱验证码(4–16 字符,来自 /api/verification)
nicknamestring否昵称,缺省同用户名

返回 data 字段:{"ok": true}。失败时 HTTP 400(邮箱格式 / 验证码无效)、409(用户名占用)、403(专属站点禁注册)。

{
  "success": true, "message": "",
  "data": {"ok": true}
}
curl -X POST http://localhost:8000/api/user/register \
  -H "Content-Type: application/json" \
  -d '{"username":"newuser","password":"password123","email":"newuser@example.com","email_code":"123456"}'
import requests
r = requests.post("http://localhost:8000/api/user/register",
                  json={"username": "newuser", "password": "password123",
                        "email": "newuser@example.com", "email_code": "123456"},
                  timeout=30)
print(r.json())  # {"success": true, "message": "", "data": {"ok": true}}
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"username\":\"newuser\",\"password\":\"password123\","
         + "\"email\":\"newuser@example.com\",\"email_code\":\"123456\"}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/register"))
    .header("Content-Type", "application/json")
    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/register", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ username: "newuser", password: "password123",
                         email: "newuser@example.com", email_code: "123456" }),
});
console.log(await resp.json());

26. 退出登录(会话)

GET/api/user/logout — 退出浏览器会话:删除服务端 session 并清除 tg_session Cookie。仅对 Cookie 会话有意义;tg-sat- 系统访问令牌是无状态的,不受本接口影响(重置令牌请用「5. 重置系统访问令牌」)。

返回 data 字段:{"ok": true}。

{
  "success": true, "message": "",
  "data": {"ok": true}
}
curl http://localhost:8000/api/user/logout \
  -b "tg_session=<会话Cookie值>"
import requests
s = requests.Session()  # 已登录的会话对象
print(s.get("http://localhost:8000/api/user/logout", timeout=30).json())
// JDK 11+(携带登录后获得的 tg_session Cookie)
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/logout"))
    .header("Cookie", "tg_session=<会话Cookie值>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/logout", {
  headers: { Cookie: "tg_session=<会话Cookie值>" },
});
console.log(await resp.json());

27. 重置密码(登录态,发新密码到邮箱)

POST/api/user/reset — 已登录用户重置自己的密码:生成随机新密码并发送到绑定邮箱(旧密码立即失效,邮件发送失败则整体回滚)。未绑定邮箱返回 400。可用 Cookie 会话或 tg-sat- + New-Api-User 调用。

返回 data 字段:ok(恒 true)、email(新密码发送到的邮箱)。

{
  "success": true, "message": "",
  "data": {"ok": true, "email": "alice@example.com"}
}
SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>
curl -X POST http://localhost:8000/api/user/reset \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<你的用户ID>"
r = requests.post("http://localhost:8000/api/user/reset",
                  headers={"Authorization": f"Bearer {SAT}",
                           "New-Api-User": UID}, timeout=30)
print(r.json()["data"])  # {"ok": true, "email": "..."}
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/reset"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>")
    .POST(java.net.http.HttpRequest.BodyPublishers.noBody()).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/reset", {
  method: "POST",
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<你的用户ID>",
  },
});
console.log((await resp.json()).data);

28. 发送邮箱验证码(公开)

GET/api/verification — 向指定邮箱发送注册验证码(供 /api/user/register 的 email_code 使用)。邮箱格式不正确返回 400;邮件服务未配置返回 503,发送失败返回 502。

参数类型必填说明
emailstring (query)是目标邮箱

返回 data 字段:ok(恒 true)、ttl(验证码有效期,秒)。

{
  "success": true, "message": "",
  "data": {"ok": true, "ttl": 600}
}
curl "http://localhost:8000/api/verification?email=newuser@example.com"
import requests
r = requests.get("http://localhost:8000/api/verification",
                 params={"email": "newuser@example.com"}, timeout=30)
print(r.json()["data"])  # {"ok": true, "ttl": 600}
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/verification?email=newuser@example.com"))
    .GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/verification?email=newuser@example.com");
console.log((await resp.json()).data);

29. 找回密码(公开,按用户名 + 邮箱)

GET/api/reset_password — 忘记密码时自助找回:校验用户名与绑定邮箱一致后,生成随机新密码发送到该邮箱(旧密码立即失效,邮件发送失败则整体回滚)。两参数均必填;不匹配统一返回 400(避免账号枚举)。

参数类型必填说明
usernamestring (query)是用户名
emailstring (query)是该用户绑定的邮箱

返回 data 字段:ok(恒 true)、email(新密码发送到的邮箱)。

{
  "success": true, "message": "",
  "data": {"ok": true, "email": "alice@example.com"}
}
curl "http://localhost:8000/api/reset_password?username=alice&email=alice@example.com"
import requests
r = requests.get("http://localhost:8000/api/reset_password",
                 params={"username": "alice", "email": "alice@example.com"},
                 timeout=30)
print(r.json()["data"])  # {"ok": true, "email": "alice@example.com"}
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/reset_password?username=alice&email=alice@example.com"))
    .GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/reset_password?username=alice&email=alice@example.com");
console.log((await resp.json()).data);

用户管理补充(管理员)

30. 用户列表(管理员)

GET/api/user/ — 分页查询全站用户(需要 admin:users:view 权限)。注意路径带尾斜杠,与「33. 用户详情」的 /api/user/{id} 区分。

参数类型必填说明
pint (query)否页码,默认 1
page_sizeint (query)否每页条数,默认 20,最大 100
keywordstring (query)否按用户名 / 昵称模糊匹配

返回 data 字段:分页信封 {"items": [...], "total": N, "page": 1, "page_size": 20};items 为管理员视角用户对象(字段同「33. 用户详情」)。

{
  "success": true, "message": "",
  "data": {
    "items": [{"id": 2, "username": "bob", "nickname": "Bob",
               "email": "bob@example.com", "balance": 100.0,
               "status": "active", "is_admin": false, "...": "..."}],
    "total": 57, "page": 1, "page_size": 20
  }
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl "http://localhost:8000/api/user/?p=1&page_size=20&keyword=alice" \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
H = {"Authorization": f"Bearer {SAT}", "New-Api-User": UID}
r = requests.get("http://localhost:8000/api/user/",
                 params={"p": 1, "page_size": 20, "keyword": "alice"},
                 headers=H, timeout=30)
data = r.json()["data"]
print(data["total"], [u["username"] for u in data["items"]])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/?p=1&page_size=20"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const H = {
  Authorization: "Bearer <tg-sat-系统访问令牌>",
  "New-Api-User": "<管理员用户ID>",
};
const resp = await fetch("http://localhost:8000/api/user/?p=1&page_size=20", { headers: H });
const d = (await resp.json()).data;
console.log(d.total, d.items.map(u => u.username));

31. 创建用户(管理员)

POST/api/user/ — 单个创建用户(需要 admin:users:batch_create 权限)。内部复用批量创建逻辑:遵循托管积分规则、自动生成默认 API Key、不发欢迎邮件。若配置了创建赠送且由管理员承担,charged_from / charged_total 反映从调用者余额划转的情况。

参数类型必填说明
usernamestring是用户名(3–64 字符)
passwordstring是密码(6–128 字符)
emailstring否邮箱,默认空
nicknamestring否昵称,默认空(同用户名)
creditsfloat否初始积分(≥ 0,默认 0)

返回 data 字段:ok(是否全部成功)、created / failed / total(成功 / 失败 / 总数)、charged_from(积分划转来源)、charged_total(划转总额)。

{
  "success": true, "message": "",
  "data": {"ok": true, "created": 1, "failed": 0, "total": 1,
           "charged_from": null, "charged_total": 0}
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl -X POST http://localhost:8000/api/user/ \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"username":"bob","password":"secret123","email":"bob@example.com","credits":100}'
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.post("http://localhost:8000/api/user/",
                  headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                  json={"username": "bob", "password": "secret123",
                        "email": "bob@example.com", "credits": 100},
                  timeout=30)
print(r.json()["data"])  # {"ok": true, "created": 1, ...}
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"username\":\"bob\",\"password\":\"secret123\","
         + "\"email\":\"bob@example.com\",\"credits\":100}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>")
    .header("Content-Type", "application/json")
    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/", {
  method: "POST",
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ username: "bob", password: "secret123",
                         email: "bob@example.com", credits: 100 }),
});
console.log((await resp.json()).data);

32. 编辑用户(管理员)

PUT/api/user/ — 编辑指定用户(需要 admin:users:edit 权限)。body 必须包含 id,其余字段只传需要修改的;与原生后台编辑共用同一核心逻辑与权限校验。返回更新后的管理员用户对象。

参数类型必填说明
idint是目标用户 ID
group_idint否所属分组 ID
statusstring否active / disabled
is_adminbool否是否超级管理员
nickname / emailstring否昵称 / 邮箱
allowed_models / denied_modelsstring[]否模型白名单 / 黑名单
role_idsint[]否角色 ID 列表
file_retention_days / log_retention_daysint否文件 / 日志保留天数
is_agent / agent_site_name / agent_logobool / string否代理商身份与专属站点配置
{
  "success": true, "message": "",
  "data": {"id": 2, "username": "bob", "nickname": "Bobby",
           "status": "active", "...": "..."}
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl -X PUT http://localhost:8000/api/user/ \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"id":2,"group_id":1}'
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.put("http://localhost:8000/api/user/",
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 json={"id": 2, "group_id": 1},
                 timeout=30)
print(r.json()["data"]["group_id"])  # 1
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"id\":2,\"group_id\":1}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>")
    .header("Content-Type", "application/json")
    .PUT(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/", {
  method: "PUT",
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 2, group_id: 1 }),
});
console.log((await resp.json()).data);

33. 用户详情(管理员)

GET/api/user/{id} — 查询单个用户详情(需要 admin:users:view 权限),含分组、角色与代理商信息。用户不存在返回 404。

返回 data 字段:管理员视角用户对象,常用字段:id、username、nickname、email、balance、rate_multiplier、group_id / group_name、status、is_admin、is_agent、created_at 等。

{
  "success": true, "message": "",
  "data": {"id": 2, "username": "bob", "nickname": "Bob",
           "email": "bob@example.com", "balance": 100.0,
           "group_id": 1, "group_name": "default",
           "status": "active", "is_admin": false, "is_agent": false, "...": "..."}
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl http://localhost:8000/api/user/2 \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.get("http://localhost:8000/api/user/2",
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
print(r.json()["data"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/2"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/2", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
  },
});
console.log((await resp.json()).data);

34. 用户搜索(管理员)

GET/api/user/search — 按关键字搜索用户(需要 admin:users:view 权限),匹配用户名 / 昵称 / 邮箱。返回裸数组(不带分页信封),最多 100 条,按用户 ID 倒序;不带 keyword 时返回最新的 100 个用户。

参数类型必填说明
keywordstring (query)否匹配用户名 / 昵称 / 邮箱
{
  "success": true, "message": "",
  "data": [
    {"id": 2, "username": "bob", "nickname": "Bob",
     "email": "bob@example.com", "balance": 100.0, "...": "..."}
  ]
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl "http://localhost:8000/api/user/search?keyword=bob" \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.get("http://localhost:8000/api/user/search",
                 params={"keyword": "bob"},
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
for u in r.json()["data"]:
    print(u["id"], u["username"], u["email"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/search?keyword=bob"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/search?keyword=bob", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
  },
});
for (const u of (await resp.json()).data) console.log(u.id, u.username, u.email);

35. 用户状态操作(管理员)

POST/api/user/manage — 对用户执行快捷操作(需要 admin:users:edit 权限)。不提供 delete:停用请用 disable。不支持的 action 返回 400,用户不存在返回 404。

参数类型必填说明
idint是目标用户 ID
actionstring是disable(停用)/ enable(启用)/ promote(设为管理员)/ demote(取消管理员)

返回 data 字段:{"ok": true, "id": N, "action": "..."}。

{
  "success": true, "message": "",
  "data": {"ok": true, "id": 2, "action": "disable"}
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl -X POST http://localhost:8000/api/user/manage \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"id":2,"action":"disable"}'
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.post("http://localhost:8000/api/user/manage",
                  headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                  json={"id": 2, "action": "disable"}, timeout=30)
print(r.json()["data"])  # {"ok": true, "id": 2, "action": "disable"}
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"id\":2,\"action\":\"disable\"}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/manage"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>")
    .header("Content-Type", "application/json")
    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/manage", {
  method: "POST",
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ id: 2, action: "disable" }),
});
console.log((await resp.json()).data);

36. 充值记录(管理员)

GET/api/user/topup — 分页查询充值记录(type=recharge 的积分流水,需要 admin:tx:view 权限),可按用户过滤。

参数类型必填说明
p / page_sizeint (query)否分页,默认 1 / 20(page_size 最大 100)
user_idint (query)否只看指定用户的充值

返回 data.items 字段:id(流水 ID)、user_id、amount(充值金额)、balance_after(到账后余额)、remark(备注)、operator_id(经办管理员)、created_at。

{
  "success": true, "message": "",
  "data": {
    "items": [{"id": 301, "user_id": 2, "amount": 100.0,
               "balance_after": 100.0, "remark": "对公转账",
               "operator_id": 1, "created_at": "2026-09-18T10:00:00"}],
    "total": 12, "page": 1, "page_size": 20
  }
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl "http://localhost:8000/api/user/topup?p=1&page_size=20&user_id=2" \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.get("http://localhost:8000/api/user/topup",
                 params={"p": 1, "page_size": 20, "user_id": 2},
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
for t in r.json()["data"]["items"]:
    print(t["id"], t["user_id"], t["amount"], t["remark"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/topup?p=1&page_size=20&user_id=2"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/topup?p=1&page_size=20&user_id=2", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
  },
});
for (const t of (await resp.json()).data.items) console.log(t.id, t.amount, t.remark);

37. 确认充值到账(管理员)

POST/api/user/topup/complete — 人工确认到账并给用户加积分(需要 admin:users:grant 权限),JSON 版授予接口;需要凭证附件时改用 /api/user/{id}/grant。remark 为财务审计必填。用户不存在返回 404。

参数类型必填说明
user_idint是目标用户 ID
amountfloat是充值金额(> 0)
remarkstring是操作说明(财务审计必填,不能为空)

返回 data 字段:ok、user_id、amount、balance(到账后余额)。

{
  "success": true, "message": "",
  "data": {"ok": true, "user_id": 2, "amount": 100.0, "balance": 200.0}
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl -X POST http://localhost:8000/api/user/topup/complete \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID" \
  -H "Content-Type: application/json" \
  -d '{"user_id":2,"amount":100,"remark":"对公转账到账"}'
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.post("http://localhost:8000/api/user/topup/complete",
                  headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                  json={"user_id": 2, "amount": 100, "remark": "对公转账到账"},
                  timeout=30)
print(r.json()["data"]["balance"])  # 到账后余额
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"user_id\":2,\"amount\":100,\"remark\":\"对公转账到账\"}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/topup/complete"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>")
    .header("Content-Type", "application/json")
    .POST(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/topup/complete", {
  method: "POST",
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ user_id: 2, amount: 100, remark: "对公转账到账" }),
});
console.log((await resp.json()).data.balance);

38. 我的可用分组(登录态)

GET/api/user/self/groups — 查询当前用户可见的启用中分组列表(登录态即可,无需管理员权限;可用 Cookie 会话或 tg-sat- + New-Api-User)。返回裸数组。

返回 data 字段(数组):id、name(分组标识)、display_name(显示名)、rate(分组倍率)、is_current(是否当前用户所属分组)。

{
  "success": true, "message": "",
  "data": [
    {"id": 1, "name": "default", "display_name": "默认分组", "rate": 1.0, "is_current": true},
    {"id": 2, "name": "vip", "display_name": "VIP", "rate": 0.8, "is_current": false}
  ]
}
SAT=<tg-sat-系统访问令牌>; UID=<你的用户ID>
curl http://localhost:8000/api/user/self/groups \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<你的用户ID>"
r = requests.get("http://localhost:8000/api/user/self/groups",
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
for g in r.json()["data"]:
    print(g["name"], g["rate"], g["is_current"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/user/self/groups"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<你的用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/user/self/groups", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<你的用户ID>",
  },
});
for (const g of (await resp.json()).data) console.log(g.name, g.rate, g.is_current);

渠道补充(管理员)

39. 全部渠道模型名(管理员)

GET/api/channel/models — 返回所有渠道已知模型名(需要 admin:providers:view 权限),含已停用条目,去重后按名称排序。返回裸数组,常用于渠道配置页的模型下拉候选。

{
  "success": true, "message": "",
  "data": ["gpt-4o", "gpt-4o-mini", "claude-sonnet-4-5", "..."]
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl http://localhost:8000/api/channel/models \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.get("http://localhost:8000/api/channel/models",
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
print(r.json()["data"])  # ["gpt-4o", "gpt-4o-mini", ...]
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/channel/models"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/channel/models", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
  },
});
console.log((await resp.json()).data);

40. 启用中模型名(管理员)

GET/api/channel/models_enabled — 返回当前启用中(供应商与目录条目均 active)的模型名(需要 admin:providers:view 权限),去重排序。返回裸数组。

{
  "success": true, "message": "",
  "data": ["gpt-4o-mini", "claude-sonnet-4-5", "..."]
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl http://localhost:8000/api/channel/models_enabled \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.get("http://localhost:8000/api/channel/models_enabled",
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
print(r.json()["data"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/channel/models_enabled"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/channel/models_enabled", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
  },
});
console.log((await resp.json()).data);

41. 同步上游模型(管理员)

GET/api/channel/fetch_models/{id} — 从指定渠道(供应商)的上游实时拉取模型列表并录入本地目录(等价原生「同步模型」,需要 admin:providers:manage 权限)。返回裸数组:同步完成后该供应商当前全部活跃模型名。渠道不存在返回 404,上游拉取失败返回 502。另有 POST/api/channel/fetch_models(body 传 base_url + key)用于建渠道前的探测,不落库。

{
  "success": true, "message": "",
  "data": ["gpt-4o", "gpt-4o-mini", "gpt-5", "..."]
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl http://localhost:8000/api/channel/fetch_models/1 \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.get("http://localhost:8000/api/channel/fetch_models/1",
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=60)  # 上游拉取可能较慢
print(r.json()["data"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/channel/fetch_models/1"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/channel/fetch_models/1", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
  },
});
console.log((await resp.json()).data);

42. 查看渠道明文密钥(管理员)

POST/api/channel/{id}/key — 查看渠道的明文密钥(需要 admin:providers:view 权限,沿用密钥明文查看权限码)。返回 data 为多行合并的明文密钥串:首行为主密钥,其余每行一个子密钥(无子密钥时只有一行)。渠道不存在返回 404。

{
  "success": true, "message": "",
  "data": "sk-main-xxxxxxxx\nsk-sub-yyyyyyyy"
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl -X POST http://localhost:8000/api/channel/1/key \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.post("http://localhost:8000/api/channel/1/key",
                  headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                  timeout=30)
print(r.json()["data"].splitlines())  # 每行一个密钥
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/channel/1/key"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>")
    .POST(java.net.http.HttpRequest.BodyPublishers.noBody()).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/channel/1/key", {
  method: "POST",
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
  },
});
console.log(((await resp.json()).data || "").split("\n"));

其他补充(Key 用量 / 日志 / 模型 / 供应商)

43. Key 用量查询(仅 sk-tg-)

GET/api/usage/token/ — 凭模型调用密钥(sk-tg-)自身鉴权查询该 Key 的累计用量。这是 /api/* 管理面中唯一接受 sk-tg- 鉴权的例外端点(不接受 tg-sat- / 登录会话,用其他凭证调用返回 403)。路径带不带尾斜杠均可。

返回 data 字段:count(该 Key 的调用次数)、quota(该 Key 累计扣费积分)、key_name(Key 名称)、key_status(Key 状态)。

{
  "success": true, "message": "",
  "data": {"count": 128, "quota": 35.6, "key_name": "default", "key_status": "active"}
}
KEY=<sk-tg-模型调用密钥>
curl http://localhost:8000/api/usage/token/ \
  -H "Authorization: Bearer $KEY"
import requests
KEY = "<sk-tg-模型调用密钥>"
r = requests.get("http://localhost:8000/api/usage/token/",
                 headers={"Authorization": f"Bearer {KEY}"}, timeout=30)
d = r.json()["data"]
print(d["count"], d["quota"], d["key_name"], d["key_status"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/usage/token/"))
    .header("Authorization", "Bearer <sk-tg-模型调用密钥>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/usage/token/", {
  headers: { Authorization: "Bearer <sk-tg-模型调用密钥>" },
});
console.log((await resp.json()).data);

44. Key 调用日志(仅 sk-tg-)

GET/api/log/token/ — 凭模型调用密钥(sk-tg-)自身鉴权分页查询该 Key 自己的调用日志(不接受 tg-sat- / 登录会话,用其他凭证调用返回 403)。登录用户查个人日志请用 /api/log/self。

参数类型必填说明
p / page_sizeint (query)否分页,默认 1 / 20(page_size 最大 100)

返回 data.items 字段:id、user_id、request_id、category、model、path、http_status、stream、prompt_tokens / completion_tokens、upstream_cost(上游成本)、credits_charged(实际扣费)、latency_ms、error、created_at 等。

{
  "success": true, "message": "",
  "data": {
    "items": [{"id": 9001, "user_id": 1, "request_id": "chatcmpl-abc",
               "category": "llm", "model": "gpt-4o-mini",
               "path": "/v1/chat/completions", "http_status": 200, "stream": false,
               "prompt_tokens": 120, "completion_tokens": 45,
               "upstream_cost": 0.00021, "credits_charged": 0.35,
               "latency_ms": 820, "error": null,
               "created_at": "2026-09-18T10:00:00"}],
    "total": 128, "page": 1, "page_size": 20
  }
}
KEY=<sk-tg-模型调用密钥>
curl "http://localhost:8000/api/log/token/?p=1&page_size=20" \
  -H "Authorization: Bearer $KEY"
import requests
KEY = "<sk-tg-模型调用密钥>"
r = requests.get("http://localhost:8000/api/log/token/",
                 params={"p": 1, "page_size": 20},
                 headers={"Authorization": f"Bearer {KEY}"}, timeout=30)
data = r.json()["data"]
for log in data["items"]:
    print(log["created_at"], log["model"], log["credits_charged"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/log/token/?p=1&page_size=20"))
    .header("Authorization", "Bearer <sk-tg-模型调用密钥>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/log/token/?p=1&page_size=20", {
  headers: { Authorization: "Bearer <sk-tg-模型调用密钥>" },
});
for (const log of (await resp.json()).data.items) {
  console.log(log.created_at, log.model, log.credits_charged);
}

45. 单个模型元数据(管理员)

GET/api/models/{id} — 按目录条目 ID 查询单个模型的元数据(需要 admin:models:view 权限),与「21. 模型元数据列表」的 items 形状一致。条目不存在返回 404。

{
  "success": true, "message": "",
  "data": {"id": 12, "model_name": "gpt-4o-mini", "description": "OpenAI 快速对话模型",
           "vendor_id": 1, "status": 1, "sort_order": 12, "provider_id": 1,
           "public_name": "gpt-4o-mini", "category": ["llm"],
           "is_public": true, "context_length": 128000}
}
SAT=<tg-sat-系统访问令牌>; UID=<管理员用户ID>
curl http://localhost:8000/api/models/12 \
  -H "Authorization: Bearer $SAT" -H "New-Api-User: $UID"
import requests
SAT = "<tg-sat-系统访问令牌>"; UID = "<管理员用户ID>"
r = requests.get("http://localhost:8000/api/models/12",
                 headers={"Authorization": f"Bearer {SAT}", "New-Api-User": UID},
                 timeout=30)
print(r.json()["data"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/models/12"))
    .header("Authorization", "Bearer <tg-sat-系统访问令牌>")
    .header("New-Api-User", "<管理员用户ID>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch("http://localhost:8000/api/models/12", {
  headers: {
    Authorization: "Bearer <tg-sat-系统访问令牌>",
    "New-Api-User": "<管理员用户ID>",
  },
});
console.log((await resp.json()).data);

46. 供应商搜索与详情(公开)

GET/api/vendors/search 与 GET/api/vendors/{id} — 「23. 价格目录与供应商」中 /api/vendors 的配套端点,均无需认证。平台为单供应商体系:搜索仅在 keyword 命中「可信词元」时返回该供应商(否则空数组);详情仅 id=1 存在,其余返回 404。

参数类型必填说明
keywordstring (query)否搜索关键字(仅 /search)
idint (path)是供应商 ID,仅 1 有效(仅 /{id})

返回 data 字段:id(恒 1)、name(「可信词元」)、description(「平台统一供应商」)、icon、status(恒 enabled)。

{
  "success": true, "message": "",
  "data": {"id": 1, "name": "可信词元", "description": "平台统一供应商",
           "icon": "", "status": "enabled"}
}
curl "http://localhost:8000/api/vendors/search?keyword=可信词元"
curl http://localhost:8000/api/vendors/1
import requests
print(requests.get("http://localhost:8000/api/vendors/search",
                   params={"keyword": "可信词元"}, timeout=30).json()["data"])
print(requests.get("http://localhost:8000/api/vendors/1", timeout=30).json()["data"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/vendors/1")).GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const search = await (await fetch("http://localhost:8000/api/vendors/search?keyword=可信词元")).json();
console.log(search.data);
const one = await (await fetch("http://localhost:8000/api/vendors/1")).json();
console.log(one.data);

用户自助 API(tg-usr- 用户令牌)

面向终端用户的自助接口:积分流水对账,以及配置自己 API Key 的模型使用时段。 tg-usr- 用户令牌与模型调用 Key(sk-tg-)、登录会话完全隔离, 把 tg-usr- 令牌交给第三方系统对账也不会泄露模型调用能力。

如何获取:登录控制台 →「我的令牌」→ 新建令牌并勾选权限范围(可随时修改/吊销):

  • user:finance:read — 财务查询(积分流水)
  • user:keys:config — Key 配置(模型使用时段)

调用方式:Authorization: Bearer tg-usr-...(无需 New-Api-User 头;登录会话 Cookie 亦可直接调用)。

1. 我的积分流水

GET/api/transaction/self — 分页查询自己的积分流水(充值 / 消费 / 授予 / 调整),每笔含变动后余额与计费上下文。这是第三方对账系统最常用的端点:拿 type=consume 的流水与自己的业务日志按 request_id 逐笔核对即可完成对账(见组合调用示例 · 工作流 1)。需要 user:finance:read 权限。

参数类型必填说明
typestring (query)否流水类型:recharge(充值)/ consume(消费)/ grant(管理员授予)/ adjust(调整)
pageint (query)否页码,默认 1
page_sizeint (query)否每页条数,默认 20,最大 100

返回 data.items 字段(TransactionOut):id(流水号)、type(类型)、amount(变动积分:消费为负、充值 / 授予为正)、balance_after(变动后余额)、multiplier(该笔计费倍率)、upstream_cost / upstream_cost_cny / exchange_rate(上游成本美元 / 人民币与汇率,消费类有值)、sale_coefficient(售价系数)、request_id(关联调用的请求 ID,充值类为 null)、remark(备注)、created_at(时间)。

{
  "success": true, "message": "",
  "data": {
    "items": [{
      "id": 5678, "user_id": 1, "type": "consume", "amount": -0.33,
      "balance_after": 999.67, "multiplier": 1.0,
      "upstream_cost": 0.000192, "upstream_cost_cny": 0.00138,
      "exchange_rate": 7.2, "sale_coefficient": 1.5,
      "request_id": "req-7f2c...", "remark": null,
      "created_at": "2026-09-18T10:24:31"
    }, {
      "id": 5001, "user_id": 1, "type": "recharge", "amount": 1000.0,
      "balance_after": 1000.0, "multiplier": null,
      "upstream_cost": null, "upstream_cost_cny": null,
      "exchange_rate": null, "sale_coefficient": null,
      "request_id": null, "remark": "在线充值",
      "created_at": "2026-09-01T09:00:00"
    }],
    "total": 233, "page": 1, "page_size": 20
  }
}
TOKEN=<tg-usr-用户令牌>
# 全部流水
curl "http://localhost:8000/api/transaction/self?page=1" \
  -H "Authorization: Bearer $TOKEN"
# 仅消费流水(对账用)
curl "http://localhost:8000/api/transaction/self?type=consume&page=1" \
  -H "Authorization: Bearer $TOKEN"
import requests
TOKEN = "<tg-usr-用户令牌>"
r = requests.get("http://localhost:8000/api/transaction/self",
                 params={"type": "consume", "page": 1},
                 headers={"Authorization": f"Bearer {TOKEN}"},
                 timeout=30)
for t in r.json()["data"]["items"]:
    print(t["created_at"], t["amount"], t["balance_after"], t["request_id"])
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/transaction/self?type=consume&page=1"))
    .header("Authorization", "Bearer <tg-usr-用户令牌>").GET().build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const resp = await fetch(
  "http://localhost:8000/api/transaction/self?type=consume&page=1",
  {
    headers: { Authorization: "Bearer <tg-usr-用户令牌>" },
  },
);
for (const t of (await resp.json()).data.items) {
  console.log(t.created_at, t.amount, t.balance_after, t.request_id);
}

2. Key 模型使用时段

约束某个 API Key 只能在指定时间调用某个模型(北京时间;只影响该 Key 的调用时间,不影响额度)。三个端点(鉴权:tg-usr- 令牌需 user:keys:config 权限,或登录会话):

  • GET/api/key/{key_id}/model_window — 该 Key 已配置的全部时段规则
  • PUT/api/key/{key_id}/model_window — 设置某模型的使用时段,请求体 {"model": "模型名", "windows": [...]}(覆盖式更新)
  • DELETE/api/key/{key_id}/model_window?model=模型名 — 删除该模型的时段(恢复不限)

时段规则语法(windows 数组,任一命中即可用,空数组 = 全天可用):

  • {"start":"09:00","end":"18:00"} — 每天时段(支持跨午夜,如 22:00-06:00)
  • {"weekdays":[0,1,2,3,4],"start":"09:00:00","end":"18:00:00"} — 按星期(0=周一…6=周日)
  • {"date":"2026-10-01","action":"closed"} — 指定日期全天关闭
  • {"date":"2026-10-09","start":"09:00","end":"12:00"} — 指定日期仅此时段开放
{
  "success": true, "message": "",
  "data": [
    {"model": "deepseek-v4-flash",
     "windows": [{"start": "09:00", "end": "18:00"}]},
    {"model": "gpt-4o-mini",
     "windows": [{"weekdays": [0,1,2,3,4], "start": "09:00:00", "end": "18:00:00"},
                 {"date": "2026-10-01", "action": "closed"}]}
  ]
}
TOKEN=<tg-usr-用户令牌>
# 查看已配置的时段
curl http://localhost:8000/api/key/123/model_window \
  -H "Authorization: Bearer $TOKEN"
# 设置:工作日 09:00-18:00 才能用 deepseek-v4-flash
curl -X PUT http://localhost:8000/api/key/123/model_window \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"model":"deepseek-v4-flash","windows":[{"weekdays":[0,1,2,3,4],"start":"09:00","end":"18:00"}]}'
# 删除时段(恢复不限)
curl -X DELETE "http://localhost:8000/api/key/123/model_window?model=deepseek-v4-flash" \
  -H "Authorization: Bearer $TOKEN"
import requests
TOKEN = "<tg-usr-用户令牌>"
H = {"Authorization": f"Bearer {TOKEN}"}
# 查看已配置的时段
r = requests.get("http://localhost:8000/api/key/123/model_window",
                 headers=H, timeout=30)
print(r.json()["data"])
# 设置时段
r = requests.put("http://localhost:8000/api/key/123/model_window",
                 headers=H,
                 json={"model": "deepseek-v4-flash",
                       "windows": [{"start": "09:00", "end": "18:00"}]},
                 timeout=30)
print(r.json()["success"])  # True
// JDK 11+
var http = java.net.http.HttpClient.newHttpClient();
var body = "{\"model\":\"deepseek-v4-flash\","
         + "\"windows\":[{\"start\":\"09:00\",\"end\":\"18:00\"}]}";
var req = java.net.http.HttpRequest.newBuilder()
    .uri(java.net.URI.create("http://localhost:8000/api/key/123/model_window"))
    .header("Authorization", "Bearer <tg-usr-用户令牌>")
    .header("Content-Type", "application/json")
    .PUT(java.net.http.HttpRequest.BodyPublishers.ofString(body)).build();
System.out.println(http.send(req,
    java.net.http.HttpResponse.BodyHandlers.ofString()).body());
const H = {
  Authorization: "Bearer <tg-usr-用户令牌>",
  "Content-Type": "application/json",
};
// 查看已配置的时段
let resp = await fetch("http://localhost:8000/api/key/123/model_window", {
  headers: H,
});
console.log((await resp.json()).data);
// 设置时段
resp = await fetch("http://localhost:8000/api/key/123/model_window", {
  method: "PUT",
  headers: H,
  body: JSON.stringify({
    model: "deepseek-v4-flash",
    windows: [{ start: "09:00", end: "18:00" }],
  }),
});
console.log((await resp.json()).success); // true

3. tg-usr- 令牌的适用范围

tg-usr- 用户令牌是最小权限令牌:它只被上文两组端点接受,其余接口一律不认。把令牌交给第三方系统时,对方能做的只有这两件事。

端点所需 scope说明
GET/api/transaction/selfuser:finance:read我的积分流水(上文条目 1)
GET/PUT/DELETE/api/key/{key_id}/model_windowuser:keys:configKey 的模型使用时段(上文条目 2)

不接受 tg-usr- 的常见场景(需登录会话 Cookie 或 tg-sat- + New-Api-User 头):

  • 令牌管理:/api/my/tokens、/api/my/token-scopes(创建 / 修改 / 吊销用户令牌本身)
  • Key 管理:/api/key 的列表 / 创建 / 修改 / 删除等
  • 其余 /api/* 管理面端点(见上文 NewAPI 兼容管理 API 章节)

同样地,模型调用密钥(sk-tg-)与运维令牌(tg-ops-)调用本组接口一律返回 401——三套令牌完全隔离。