# 开放 API

> 一个与 OpenAI 兼容的接口，用来调用你的角色。

Source: https://reverie.im/zh/docs/integrations/open-api
Language: zh

Reverie 提供一个与 OpenAI 兼容的 Chat Completions 接口。把任意 OpenAI 客户端指向它，带上一个 Reverie API 密钥，然后用 `model` 字段填**角色或情景**，而不是某个大模型。对话、记忆和摘要都由 Reverie 保存在它那一侧，所以每次请求只需要带上你的新消息。

任何已登录的账号都能用。不需要什么订阅档位，也没有等候名单——你只需要一个 API 密钥，仅此而已。[去创建 →](https://reverie.im/zh/docs/integrations/api-keys)

## 基址与鉴权 \[#base-url-and-auth]

```
https://reverie.im/api/device/v1
Authorization: Bearer rk_...
```

**设置 → 更多 → API 密钥**里的 **API Endpoint** 卡片会显示准确的基址，旁边有复制按钮。

> **这是服务端 API。** 跨源的浏览器请求会被拦截，所以从你自己的网页发起的 fetch 无论密钥对不对都会失败。请从后端调用，并且别把密钥放进客户端代码里。

## 快速上手 \[#quick-start]

**cURL**

```bash
curl --no-buffer https://reverie.im/api/device/v1/chat/completions \
  -H "Authorization: Bearer $REVERIE_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary '{
  "model": "Luna [abc12345]",
  "messages": [
    {
      "role": "user",
      "content": "Hi"
    }
  ],
  "stream": true,
  "temperature": 0.8
}'
```

**Python**

```python
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["REVERIE_API_KEY"],
    base_url="https://reverie.im/api/device/v1",
)

stream = client.chat.completions.create(
    model="Luna [abc12345]",   # an id from GET /models, a full character or scenario ID, or omit entirely
    messages=[{"role": "user", "content": "Hi"}],
    stream=True,
    temperature=0.8,
)

for chunk in stream:
    print(chunk.choices[0].delta.content or "", end="")
```

**TypeScript**

```ts
import OpenAI from "openai";

const client = new OpenAI({
	apiKey: process.env.REVERIE_API_KEY,
	baseURL: "https://reverie.im/api/device/v1",
});

const stream = await client.chat.completions.create({
	model: "Luna [abc12345]",
	messages: [{ role: "user", content: "Hi" }],
	stream: true,
	temperature: 0.8,
});

for await (const chunk of stream) {
	process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}
```

**设置 → 更多 → API 密钥**里还有一个请求构建器，会用你真实的某个密钥和一个真实角色生成上面这三段代码。

## `model` 字段填的是角色 \[#the-model-field-names-a-character]

> **光写角色名字是不行的。** `model: "Luna"` 并不会去查一个叫 Luna 的角色。它匹配不到任何东西，于是落到密钥的默认角色或者你最近的那次聊天上，你会收到另一个角色的回复，而且不会报错。请始终使用 `GET /models` 返回的 `名字 [短ID]` 形式。

| `model` 的取值                             | 谁来回答                      |
| --------------------------------------- | ------------------------- |
| `Luna [abc12345]`                       | ID 以这 8 个字符结尾的那个角色或情景     |
| 完整的角色或情景 ID                             | 那个角色或情景                   |
| 一个 Reverie 模型 ID，例如 `deepseek-v4-flash` | 选的是**大模型**；角色来自密钥或你上一次的聊天 |
| 不填                                      | 密钥绑定的角色，否则是你最近的那次聊天       |

决定短 ID 能不能解析出来的几个细节：

* `[...]` 必须在字符串的**末尾**。
* 长度不超过 8 个字符的后缀，只会在**你已经和它有过聊天**的角色里匹配，无论是在网页上还是通过 API。你从没打开过的角色，它的短 ID 会悄悄落空。
* 只有当密钥绑定了某个角色时，才会去查情景。用全局密钥时，短情景 ID 永远解析不出来。
* 超过 8 个字符会被当成完整 ID。

### 列出你能对话的对象 \[#listing-what-you-can-talk-to]

```bash
curl https://reverie.im/api/device/v1/models \
  -H "Authorization: Bearer $REVERIE_API_KEY"
```

**全局**密钥返回你最近的角色。**绑定了角色**的密钥则返回那个角色的情景，如果它一个情景都没有，就返回角色本身。每一条看起来是这样：

```json
{
	"id": "Luna [abc12345]",
	"object": "model",
	"created": 1759200000,
	"owned_by": "character",
	"permission": [],
	"root": "<full character id>",
	"parent": null
}
```

把 `id` 原样复制进 `model` 就行。这个接口的报错用的是朴素的错误结构，而不是 OpenAI 的外壳。

## 请求体里哪些字段会被读取 \[#what-the-request-body-reads]

只有四个字段会被读取。其余的都会被接受，然后悄悄丢掉。

| 字段            | 类型      | 默认值              | 说明                       |
| ------------- | ------- | ---------------- | ------------------------ |
| `messages`    | 数组      | 必填               | 见下——它只有一部分会被用到           |
| `model`       | 字符串     | 可选               | 角色、情景或大模型，如上所述           |
| `stream`      | 布尔      | **`true`**       | 和 OpenAI API 不同，这里默认就是流式 |
| `temperature` | 0–2 的数字 | 角色自己的设置，否则 `0.8` | 超出范围的值会被拒绝               |

会被忽略且不提示的字段： `max_tokens`, `top_p`, `n`, `stop`, `presence_penalty`, `frequency_penalty`, `logit_bias`, `seed`, `user`, `response_format`, `tools`, `tool_choice`, `logprobs`, `stream_options`.

`messages` 是怎么处理的：

* **`system` 消息会被丢掉。** 人设和行为来自角色自己的字段，而不是请求。[提示词字段 →](https://reverie.im/zh/docs/characters/prompt-fields)
* **不支持视觉。** 内容可以是字符串，也可以是分块数组，但只有 `text` 块会被读取；图片块会被忽略。
* 只有**最后一条 `user` 消息**会被加进对话。更早的用户消息会被忽略。
* 助手消息只用于同步历史（见下文）。
* 最后一条用户消息如果为空或只有空白，会返回 `missing_user_message`。

## 响应 \[#responses]

非流式响应是一个普通的 `chat.completion` 对象。`finish_reason` 永远是 `stop`——内部的工具调用从不暴露出来——而且 `usage` 计数全是零，所以请从 API 密钥页面上的用量统计来衡量 token，而不是看响应。

流式响应是 `text/event-stream`：

1. 内容块：带 `delta.content` 的 `chat.completion.chunk` 对象。\*&#x2A;不会发送开头那个 `delta: {"role": "assistant"}` 块。*\*
2. 一个结束块，`finish_reason` 是 `stop`、`length` 或 `content_filter`。因工具调用而结束的情况会报成 `stop`。
3. 一个 `choices` 为空数组的用量块。
4. `data: [DONE]`.

如果生成过程中出错，流会先发出 `data: {"error": {...}}`，然后是 `[DONE]`。

## 对话状态 \[#conversation-state]

对话由 Reverie 保存。每一对「密钥 + 角色」对应一个聊天，它会和你其他的聊天一起出现在网页应用里；每一轮都由角色的系统提示词和情景、世界书设定、长期记忆、滚动摘要、角色的叙述风格、回复长度和 NSFW 设置、来自你账号语言的语言锁定，以及模型上下文窗口容得下的那部分近期历史拼成。当模型支持工具时，记忆工具会运行。

既然历史由服务端保管，你就不需要把它重放一遍。

> **发送助手消息可能会删掉历史。** 如果你的 `messages` 数组里含有助手消息，Reverie 会取最后一条，在最近的 20 条已存消息里找它，一旦找到，就会永久删除在它之后产生的每一条消息，以及那之后产生的记忆。如果没找到，就什么都不会删。你的数组有多长并不重要。想彻底避免这件事，就只发新的那条用户消息。

## 由哪个大模型作答 \[#which-llm-answers]

由角色决定，而不是由请求决定——除非你在请求里点名某个大模型。优先级从低到高：默认聊天模型，然后是角色自己的模型设置，然后是作为 `model` 传进来的 Reverie 模型 ID，最后是你在网页应用里为这个聊天单独设定的模型偏好，它压过前面所有。[模型 →](https://reverie.im/zh/docs/reference/models)

## 错误 \[#errors]

`/chat/completions` 的错误使用 OpenAI 的外壳： `{"error": {"message", "type", "param": null, "code"}}`.

| HTTP | `code`                 | `type`                  | 原因                                    |
| ---- | ---------------------- | ----------------------- | ------------------------------------- |
| 401  | `invalid_api_key`      | `invalid_request_error` | `Authorization` 缺失或格式不对、密钥不存在，或者密钥已停用 |
| 401  | `api_key_expired`      | `invalid_request_error` | 密钥已经过了有效期                             |
| 404  | `scenario_not_found`   | `invalid_request_error` | 情景 ID 解析出来了，但那个情景已经没了                 |
| 404  | `character_not_found`  | `invalid_request_error` | 角色不存在或已被删除                            |
| 404  | `no_chat_history`      | `invalid_request_error` | 没有 `model`，没有绑定角色，也没有可以回退的历史聊天        |
| 400  | `missing_user_message` | `invalid_request_error` | 没有非空的 `user` 消息                       |
| 429  | `rate_limit_exceeded`  | `rate_limit_exceeded`   | 免费模型的时段配额用完了。`Retry-After` 会给出还要等多少秒  |
| 429  | `insufficient_quota`   | `insufficient_quota`    | 积分不够这次请求                              |
| 502  | `MODEL_EMPTY_RESPONSE` | `server_error`          | 模型正常结束了，但没有产出文本                       |
| 不定   | 供应商的错误码                | `server_error`          | 上游生成失败                                |

有两类情况不走这个外壳：`GET /models` 的错误，以及被校验拒掉的请求体（比如超出范围的 `temperature`）——这两种都返回朴素的错误结构。

## 速率限制与配额 \[#rate-limits-and-quotas]

这个 API **没有按密钥的速率限制、没有并发上限，也没有请求配额**。约束你的只有两样东西：

| 约束     | 数值                                                           |
| ------ | ------------------------------------------------------------ |
| 积分余额   | 每次请求前都会检查；不够则返回 `insufficient_quota`                         |
| 免费模型时段 | 每个账号每 3 小时 15 条消息——Pro ×3（45）、Premium ×4（60）、Ultimate ×5（75） |

免费模型的时段配额和网页聊天、故事、小说以及机器人共用——它是每个账号一份，而不是每个入口各一份。[免费模型 →](https://reverie.im/zh/docs/reference/free-models)

Reverie 不会向外发送 webhook；没有什么可以订阅来接收生成完成的事件。

## 导入角色 \[#importing-characters]

还有第二个接口 `POST /api/v1/characters/import`，用角色卡创建一个角色。它需要带导入权限的密钥，而那不是设置页面能创建的那种密钥。[API 密钥 →](https://reverie.im/zh/docs/integrations/api-keys) 以及 [导入角色 →](https://reverie.im/zh/docs/characters/import-characters)

## 相关 \[#related]

* [API 密钥](https://reverie.im/zh/docs/integrations/api-keys) — 创建、绑定、设过期和查看密钥用量
* [模型](https://reverie.im/zh/docs/reference/models) — 模型 ID 和积分倍率
* [限制](https://reverie.im/zh/docs/reference/limits) — 产品里其他的上限
* [积分](https://reverie.im/zh/docs/billing/credits) — 请求是怎么计价的
