# QXAI 接入文档

版本：3.3.0

## 1. 基本信息

```text
OpenAI 兼容 Base URL：https://你的域名/org/v1
鉴权：Authorization: Bearer sk-...
```

站点实际域名以部署为准。把下面示例中的 `https://qxai-org.xioru.com` 替换成你的域名。

```text
GET  /org/v1/models
POST /org/v1/chat/completions
GET  /org/v1/balance
```

`/org/v2`、`/org/v3` 以及后台创建的接口组使用同样的协议。模型 ID 决定内部路由，调用方看不到上游。

## 返回 HTML / `Unexpected JSON token` 排查

如果客户端报：

```text
Unexpected JSON token at offset 13 ... JSON input: <html><meta charset="utf-8" /><title>Nginx...
```

说明请求拿到的是网页，不是 JSON。按顺序检查：

1. **看响应头有没有 `X-QXAI-Gateway`**

```bash
curl -i https://你的域名/org/v1/models -H "Authorization: Bearer sk-..."
```

- 有 `X-QXAI-Gateway: 3.1.8` → 请求已经进入 PHP，返回的是站内 JSON 错误，按 `error.code` 处理
- 没有这个头，页面标题是 Nginx / 防火墙 / 缓存 → 请求在 PHP 之前就被 Nginx、WAF 或 CDN 拦截了

2. **Nginx / 宝塔 / CDN 侧需要放行**

- 对 `/org/`、`/api/` 关闭缓存
- 防火墙 / WAF 放行 API 路径，不要对 POST JSON 做页面级拦截
- 提高超时，流式接口关闭缓冲：

```nginx
# 合并到已有的 location /org/ 与 location /api/ 中
fastcgi_read_timeout 600s;
fastcgi_send_timeout 600s;
fastcgi_buffering off;
add_header Cache-Control "no-store" always;

# 让 Nginx 自己的 502/504 也返回 JSON，而不是 HTML 错误页
error_page 502 503 504 = @qxai_api_error;
location @qxai_api_error {
    default_type application/json;
    return 502 '{"error":{"message":"网关暂时不可用，请稍后重试","type":"api_error","code":"bad_gateway"}}';
}
```

3. **确认 Base URL 没有多写或少写路径**

OpenAI 兼容客户端通常会自动拼接 `/chat/completions`，所以 Base URL 只写到 `/org/v1`：

```text
正确：https://你的域名/org/v1
错误：https://你的域名/org/v1/chat/completions
错误：https://你的域名/org/v1/chat/completions/chat/completions
错误：https://你的域名/api
```

3.1.9 起，重复的 `/v1`、重复的 `/chat/completions` 会自动归一化；未知路径会返回 JSON 404，并带上当前路径，方便定位。

4. **确认上游 Base URL**

- OpenAI 兼容：`https://api.xxx.com/v1`
- Anthropic：`https://api.anthropic.com`
- Google：`https://generativelanguage.googleapis.com`
- Azure：资源 Endpoint + Deployment 名

从 3.1.8 起，站内网关只要收到 HTML 就会拦截并返回 `upstream_html_response`，不会再把它透传给客户端。

## 模型列表扩展字段

`GET /org/v1/models` 在标准字段之外，额外返回模型的类型信息（一个模型可以同时属于多个类型）：

```json
{
  "id": "multi-model",
  "object": "model",
  "created": 1790000000,
  "owned_by": "site",
  "model_type": "chat,reasoning,image",
  "model_types": ["chat", "reasoning", "image"],
  "input_type": "text,vision",
  "input_types": ["text", "vision"]
}
```

- `model_types` 可能包含：`chat`、`reasoning`、`embedding`、`image`、`audio`
- `input_types` 可能包含：`text`、`vision`、`audio`、`multimodal`
- 只有包含 `chat` 或 `reasoning` 的模型才能调用 `/chat/completions`
- 原 `model_type`、`input_type` 字符串保留，兼容旧客户端

## 2. 聊天补全

标准 OpenAI Chat Completions，支持：

- 非流式与流式 `stream: true`
- 系统 / 用户 / 助手 / tool 消息
- `tools` / `tool_choice` / `parallel_tool_calls`
- 多模态 content parts
- `reasoning_content`、`reasoning`
- `temperature`、`top_p`、`max_tokens`、`max_completion_tokens`
- `response_format`、`stop`、`seed`、`logprobs` 等常见参数
- `reasoning_effort`：`low`、`medium`、`high`、`xhigh`、`max`

### 工具调用

支持 OpenAI 的 `tools` / `tool_choice` / `parallel_tool_calls`，也兼容旧版 `functions` / `function_call`。

- OpenAI 兼容上游：参数与 `tool_calls` 原样透传
- Anthropic：自动转换为 `tool_use` / `tool_result`
- Google Gemini：自动转换为 `functionDeclarations` / `functionCall` / `functionResponse`
- 流式返回的 `tool_calls` 会包含 `index` 字段

Base URL 建议写到 `/v1`，例如 `https://api.example.com/v1`。只填域名时系统会自动尝试 `/v1` 与根路径；如果上游返回网页（HTML），接口会返回 `upstream_html_response` 而不是把 HTML 透传。

请求：

```bash
curl https://qxai-org.xioru.com/org/v1/chat/completions \
  -H "Authorization: Bearer sk-你的Key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "demo-chat",
    "messages": [
      {"role": "system", "content": "You are helpful."},
      {"role": "user", "content": "你好"}
    ],
    "stream": false
  }'
```

流式：

```bash
curl -N https://qxai-org.xioru.com/org/v1/chat/completions \
  -H "Authorization: Bearer sk-你的Key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "demo-chat",
    "messages": [{"role":"user","content":"你好"}],
    "stream": true
  }'
```

服务端会向上游注入 `stream_options.include_usage`，上游不支持时自动降级。返回内容中的 `model` 会替换为公开模型 ID，上游字段会被剥离。

如果上游对 `reasoning_effort`、`max_tokens` 等可选参数返回 400/422，QXAI 会自动移除可选参数并重试一次，保留 `messages`、`tools`、`tool_choice` 等关键参数。

## 3. 工具调用

请求必须是标准 OpenAI Tools 格式：

```json
{
  "model": "demo-chat",
  "messages": [
    {"role": "user", "content": "Use the get_current_time tool."}
  ],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "get_current_time",
        "description": "Get the current date and time.",
        "parameters": {
          "type": "object",
          "properties": {}
        }
      }
    }
  ]
}
```

如果返回：

```json
{
  "error": {
    "message": "当前模型未开启工具调用，或上游不接受 tools 参数；请确认该模型支持 Tool Calls。",
    "type": "invalid_request_error",
    "code": "tool_calls_not_supported"
  }
}
```

说明上游模型本身不支持 Tools，或者后台模型配置的上游模型 ID 不支持。QXAI 会把标准参数原样转发，不会把原生 400 错误体暴露给用户。请更换支持 Tool Calls 的模型，或在 RikkaHub 中关闭该模型的能力测试项。

## 4. 余额查询

推荐路径：

```text
GET /api/user/balance
GET /org/v1/balance
```

返回：

```json
{
  "balance": "12.50",
  "used": "3.2000",
  "currency": "肥料"
}
```

兼容主流服务商的额外面：

| 路径 | 返回格式 |
| --- | --- |
| `/org/v1/balance` | `{balance, used, currency}` |
| `/org/v1/user/balance` | DeepSeek 风格 `balance_infos[0].total_balance` |
| `/org/v1/users/me/balance` | Moonshot 风格 `data.available_balance` |
| `/org/v1/credits` | RikkaHub 兼容：`data.total_usage` = 当前余额 |
| `/api/user/balance` | 标准格式 |
| `/api/user/quota` | 标准格式 |
| `/api/user/wallet` | 标准格式 |
| `/api/user/credits` | OpenRouter 风格 |
| `/api/credits` | OpenRouter 风格 |
| `/v1/balance` | 标准格式 |
| `/v1/user/balance` | DeepSeek 风格 |
| `/v1/users/me/balance` | Moonshot 风格 |
| `/v1/credits` | RikkaHub 兼容 |

DeepSeek 风格：

```json
{
  "is_available": true,
  "balance_infos": [
    {
      "currency": "肥料",
      "total_balance": "12.50",
      "granted_balance": "0.00",
      "topped_up_balance": "12.50"
    }
  ]
}
```

Moonshot 风格：

```json
{
  "code": 0,
  "status": true,
  "data": {
    "available_balance": 12.5,
    "voucher_balance": 0,
    "cash_balance": 12.5
  }
}
```

## 5. RikkaHub 配置

在 RikkaHub 中选择 OpenAI 类型提供商：

```text
Base URL：https://qxai-org.xioru.com/org/v1
API Key：sk-你的Key
```

余额查询设置为：

```text
URL：{{baseUrl}}/balance
Header：Authorization: Bearer {{apiKey}}
JSON 路径：balance
```

如果保留 RikkaHub 默认的 URL `{{baseUrl}}/credits` 和 JSON 路径 `data.total_usage`，QXAI 也做了兼容：此时 `data.total_usage` 返回的是**当前余额**，不是已消费金额。推荐仍使用 `/balance` + `balance`。

如果使用 DeepSeek 风格：

```text
URL：{{baseUrl}}/user/balance
JSON 路径：balance_infos[0].total_balance
```

如果使用 Moonshot 风格：

```text
URL：{{baseUrl}}/users/me/balance
JSON 路径：data.available_balance
```

RikkaHub 的「测试连接」有三项：

1. 非流式
2. 流式
3. 工具调用

前两项正常、第三项 400，通常不是 Base URL 或 Key 错，而是当前模型没有开启工具调用。请确认：

- RikkaHub 里该模型开启了 Tool 能力；
- 后台配置的模型确实支持 Tool Calls；
- 上游模型 ID 没有填错。

QXAI 不会伪造工具调用；工具事件的返回完全取决于上游模型。

## 6. 错误结构

```json
{
  "error": {
    "message": "错误说明",
    "type": "invalid_request_error",
    "param": null,
    "code": "error_code"
  }
}
```

不会向用户返回上游域名、品牌或原始错误体。

## 7. 速率与额度

- 所有 API Key 共享账户余额、RPM、TPM。
- 每个用户最多 5 个 Key。
- 余额不足返回 402。
- 触发限流返回 429，并带 `Retry-After`。
- 流式请求停止会按已产生内容/usage 结算。


## 8. 如果第三方收到 HTML

如果客户端报：

```text
Unexpected JSON token at offset 11 ... <!doctype html>
```

说明请求没有进 API 路由，而是落到了首页或 404 页面。请检查：

1. RikkaHub 的 Base URL 必须是：

```text
https://qxai-org.xioru.com/org/v1
```

不能只填域名。QXAI 3.0.6 已兼容只填域名和 `/org` 的情况，但仍推荐完整 `/org/v1`。

2. 宝塔 Nginx 必须让无后缀路径进入 `index.php`。在「网站 → 设置 → 伪静态」加入 `nginx.rewrite.conf` 的内容，核心是：

```nginx
location /org/ {
    try_files $uri $uri/ /index.php?$query_string;
}
location /v1/ {
    try_files $uri $uri/ /index.php?$query_string;
}
location = /chat/completions {
    try_files "" /index.php?$query_string;
}
```

3. 确认 Authorization 能透传：

```nginx
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
```

只要 Base URL 正确加上 `/org/v1`，并让 Nginx 回退到 `index.php`，第三方就不会再拿到 HTML。


## 9. 上游协议

后台「上游渠道」支持四种协议：

- OpenAI 兼容：`Authorization: Bearer`，`/chat/completions`
- Azure OpenAI：`api-key`，`/openai/deployments/{deployment}/chat/completions`
- Anthropic Messages：`x-api-key` + `anthropic-version`
- Google Gemini：`x-goog-api-key`，`generateContent`

模型加载、对话和工具调用会按上游协议转换。OpenAI/Azure 保持原生 SSE；Anthropic/Google 使用兼容适配后以 SSE 返回。

## 10. 注册、邮件与极验

- 注册：用户名、邮箱、密码、二次确认。
- 点击「发送验证邮件」后创建待验证账号，并发送 24 小时有效的验证链接。
- 用户点击链接进入 `/verify.php`，再点击「验证账户」后才激活账号。
- Resend：后台填写 API Key、已验证域名、发件邮箱、发件人名称，可测试发件。
- 极验 4.0：后台填写 Captcha ID 与 Key，可测试验证；发送验证邮件时服务端执行官方二次校验。
- 邮件冷却：同一邮箱/IP 至少 120 秒，记录在 `email_cooldowns`，刷新页面不会重置。

## 11. 模型管理

后台模型按上游厂商分组，支持搜索、全选、批量公开/取消公开/删除。删除上游会连带删除该厂商下全部模型，历史调用记录保留。
