# Open API

> Một endpoint tương thích OpenAI dành cho nhân vật của bạn.

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

Reverie mở một endpoint Chat Completions tương thích với OpenAI. Trỏ bất kỳ client OpenAI nào vào đó, gửi kèm một khóa API Reverie, và dùng trường `model` để nêu tên một **nhân vật hoặc kịch bản** thay vì một LLM. Reverie giữ cuộc trò chuyện, ký ức và các bản tóm tắt ở phía nó, nên mỗi yêu cầu chỉ cần mang theo tin nhắn mới của bạn.

Bất kỳ tài khoản nào đã đăng nhập đều dùng được. Không có hạng đăng ký, không có danh sách chờ — bạn cần một khóa API, thế thôi. [Tạo một khóa →](https://reverie.im/vi/docs/integrations/api-keys)

## URL gốc và xác thực \[#base-url-and-auth]

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

**Cài đặt → Thêm → API Keys** hiện đúng URL gốc trong thẻ **API Endpoint**, kèm một nút sao chép.

> **Đây là API phía máy chủ.** Các yêu cầu từ trình duyệt ở nguồn khác đều bị chặn, nên một lệnh fetch từ trang web của chính bạn sẽ thất bại dù khóa có đúng đi nữa. Hãy gọi nó từ backend, và giữ khóa ngoài mã phía client.

## Bắt đầu nhanh \[#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 ?? "");
}
```

**Cài đặt → Thêm → API Keys** còn có một bộ dựng yêu cầu, sinh ra đúng ba đoạn mã này từ một khóa thật của bạn và một nhân vật thật.

## Trường `model` nêu tên một nhân vật \[#the-model-field-names-a-character]

> **Chỉ ghi tên nhân vật thì không được.** `model: "Luna"` không đi tìm nhân vật tên Luna. Nó không khớp với gì cả, rơi xuống nhân vật mặc định của khóa hoặc cuộc trò chuyện gần nhất của bạn, và bạn nhận về câu trả lời của một nhân vật khác mà chẳng có lỗi nào. Hãy luôn dùng dạng `Tên [idngắn]` mà `GET /models` trả về.

| Giá trị của `model`                                   | Ai trả lời                                                                   |
| ----------------------------------------------------- | ---------------------------------------------------------------------------- |
| `Luna [abc12345]`                                     | Nhân vật hoặc kịch bản có ID kết thúc bằng 8 ký tự đó                        |
| Một ID nhân vật hoặc kịch bản đầy đủ                  | Chính nhân vật hoặc kịch bản ấy                                              |
| Một ID mô hình của Reverie, ví dụ `deepseek-v4-flash` | Chọn **LLM**; nhân vật thì lấy từ khóa hoặc cuộc trò chuyện gần nhất của bạn |
| Bỏ trống                                              | Nhân vật được ghim vào khóa, nếu không thì cuộc trò chuyện gần nhất của bạn  |

Những chi tiết quyết định một ID ngắn có tra ra được hay không:

* `[...]` phải nằm ở **cuối** chuỗi.
* Hậu tố dài 8 ký tự trở xuống chỉ được đối chiếu với những nhân vật **bạn đã có cuộc trò chuyện**, trên web hay qua API. ID ngắn của một nhân vật bạn chưa từng mở sẽ trượt đi trong im lặng.
* Việc tra kịch bản chỉ chạy khi khóa được ghim vào một nhân vật. Với khóa toàn cục, một ID kịch bản ngắn không bao giờ tra ra được.
* Dài hơn 8 ký tự thì được coi là ID đầy đủ.

### Liệt kê những ai bạn có thể trò chuyện \[#listing-what-you-can-talk-to]

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

Khóa **toàn cục** trả về các nhân vật gần đây của bạn. Khóa **ghim vào một nhân vật** thì trả về các kịch bản của nhân vật ấy, hoặc chính nhân vật nếu nó không có kịch bản nào. Mỗi mục trông như thế này:

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

Chép thẳng `id` vào `model`. Lỗi từ endpoint này dùng một dạng lỗi đơn giản chứ không phải lớp vỏ của OpenAI.

## Phần thân yêu cầu đọc những gì \[#what-the-request-body-reads]

Có bốn trường được đọc. Mọi thứ khác đều được nhận rồi lặng lẽ bỏ đi.

| Trường        | Kiểu    | Mặc định                                    | Ghi chú                                                      |
| ------------- | ------- | ------------------------------------------- | ------------------------------------------------------------ |
| `messages`    | mảng    | bắt buộc                                    | Xem bên dưới — chỉ một phần của nó được dùng                 |
| `model`       | chuỗi   | tùy chọn                                    | Nhân vật, kịch bản hoặc LLM, như trên                        |
| `stream`      | boolean | **`true`**                                  | Khác với API của OpenAI, ở đây truyền theo luồng là mặc định |
| `temperature` | số 0–2  | thiết lập của nhân vật, nếu không thì `0.8` | Giá trị ngoài khoảng sẽ bị từ chối                           |

Bị bỏ qua mà không báo: `max_tokens`, `top_p`, `n`, `stop`, `presence_penalty`, `frequency_penalty`, `logit_bias`, `seed`, `user`, `response_format`, `tools`, `tool_choice`, `logprobs`, `stream_options`.

`messages` được xử lý ra sao:

* **Tin nhắn `system` bị bỏ.** Tính cách và cách hành xử đến từ chính các trường của nhân vật, không phải từ yêu cầu. [Các trường prompt →](https://reverie.im/vi/docs/characters/prompt-fields)
* **Không hỗ trợ hình ảnh.** Nội dung có thể là chuỗi hoặc mảng các phần, nhưng chỉ phần `text` được đọc; phần ảnh bị bỏ qua.
* Chỉ **tin nhắn `user` cuối cùng** được thêm vào cuộc trò chuyện. Các tin nhắn người dùng trước đó bị bỏ qua.
* Tin nhắn của trợ lý chỉ dùng để đồng bộ lịch sử (xem bên dưới).
* Tin nhắn người dùng cuối cùng mà rỗng hoặc chỉ có khoảng trắng sẽ nhận về `missing_user_message`.

## Phản hồi \[#responses]

Phản hồi không theo luồng là một đối tượng `chat.completion` bình thường. `finish_reason` luôn là `stop` — các lệnh gọi công cụ bên trong không bao giờ lộ ra — và các bộ đếm `usage` đều bằng không, nên hãy đo token bằng thống kê sử dụng ở trang khóa API chứ đừng đo từ phản hồi.

Phản hồi theo luồng là `text/event-stream`:

1. Các khối nội dung: đối tượng `chat.completion.chunk` có `delta.content`. **Không có khối mở đầu `delta: {"role": "assistant"}` nào được gửi.**
2. Một khối kết thúc, với `finish_reason` là `stop`, `length` hoặc `content_filter`. Kết thúc do gọi công cụ cũng được báo là `stop`.
3. Một khối sử dụng với mảng `choices` rỗng.
4. `data: [DONE]`.

Nếu việc sinh nội dung hỏng giữa chừng, luồng phát ra `data: {"error": {...}}` rồi tới `[DONE]`.

## Trạng thái cuộc trò chuyện \[#conversation-state]

Reverie lưu cuộc trò chuyện. Mỗi cặp khóa–nhân vật có đúng một cuộc trò chuyện, nó hiện trong ứng dụng web bên cạnh các cuộc trò chuyện khác của bạn, và mỗi lượt được dựng từ prompt hệ thống và kịch bản của nhân vật, phần chính truyện trong sách thế giới, ký ức dài hạn, các bản tóm tắt cuộn dần, lối kể của nhân vật, độ dài câu trả lời và thiết lập NSFW, một khóa ngôn ngữ lấy từ ngôn ngữ tài khoản của bạn, cùng với lượng lịch sử gần đây nhiều nhất mà cửa sổ ngữ cảnh của mô hình chứa nổi. Các công cụ ký ức chạy khi mô hình hỗ trợ công cụ.

Vì máy chủ giữ lịch sử, bạn không cần gửi lại nó.

> **Gửi tin nhắn của trợ lý có thể xóa lịch sử.** Nếu mảng `messages` của bạn chứa tin nhắn của trợ lý, Reverie lấy tin cuối cùng, tìm nó trong 20 tin nhắn đã lưu gần nhất, và nếu khớp thì xóa vĩnh viễn mọi tin nhắn tạo sau nó, cùng với ký ức sinh ra sau thời điểm ấy. Nếu không khớp gì thì không xóa gì cả. Mảng của bạn dài bao nhiêu cũng không liên quan. Muốn tránh hẳn chuyện này, chỉ gửi tin nhắn mới của người dùng.

## LLM nào trả lời \[#which-llm-answers]

Nhân vật quyết định, chứ không phải yêu cầu — trừ khi bạn tự nêu tên một LLM. Thứ tự ưu tiên từ thấp lên cao: mô hình trò chuyện mặc định, rồi thiết lập mô hình của chính nhân vật, rồi ID mô hình Reverie truyền trong `model`, và cuối cùng là lựa chọn mô hình bạn đặt riêng cho cuộc trò chuyện đó trong ứng dụng web, thứ thắng tất cả. [Mô hình →](https://reverie.im/vi/docs/reference/models)

## Lỗi \[#errors]

Lỗi từ `/chat/completions` dùng lớp vỏ của OpenAI: `{"error": {"message", "type", "param": null, "code"}}`.

| HTTP           | `code`                 | `type`                  | Nguyên nhân                                                                            |
| -------------- | ---------------------- | ----------------------- | -------------------------------------------------------------------------------------- |
| 401            | `invalid_api_key`      | `invalid_request_error` | Thiếu `Authorization` hoặc sai định dạng, khóa lạ, hoặc khóa đang tắt                  |
| 401            | `api_key_expired`      | `invalid_request_error` | Khóa đã quá hạn                                                                        |
| 404            | `scenario_not_found`   | `invalid_request_error` | ID kịch bản tra ra được nhưng kịch bản đã biến mất                                     |
| 404            | `character_not_found`  | `invalid_request_error` | Nhân vật không còn hoặc đã bị xóa                                                      |
| 404            | `no_chat_history`      | `invalid_request_error` | Không có `model`, không có nhân vật ghim, và không có cuộc trò chuyện cũ nào để lui về |
| 400            | `missing_user_message` | `invalid_request_error` | Không có tin nhắn `user` nào khác rỗng                                                 |
| 429            | `rate_limit_exceeded`  | `rate_limit_exceeded`   | Đã cạn cửa sổ mô hình miễn phí. `Retry-After` cho biết phải chờ bao nhiêu giây         |
| 429            | `insufficient_quota`   | `insufficient_quota`    | Không đủ tín dụng cho yêu cầu này                                                      |
| 502            | `MODEL_EMPTY_RESPONSE` | `server_error`          | Mô hình kết thúc gọn gàng nhưng không có chữ nào                                       |
| tùy trường hợp | mã của nhà cung cấp    | `server_error`          | Việc sinh nội dung hỏng ở phía trên                                                    |

Hai trường hợp nằm ngoài lớp vỏ đó: lỗi của `GET /models`, và phần thân yêu cầu bị khâu kiểm tra từ chối (chẳng hạn `temperature` ngoài khoảng) — cả hai đều trả về dạng lỗi đơn giản.

## Giới hạn tần suất và hạn mức \[#rate-limits-and-quotas]

API này **không có giới hạn tần suất theo khóa, không có trần số yêu cầu đồng thời, và không có hạn mức yêu cầu**. Chỉ hai thứ ràng buộc mức dùng của bạn:

| Ràng buộc               | Giá trị                                                                                           |
| ----------------------- | ------------------------------------------------------------------------------------------------- |
| Số dư tín dụng          | Được kiểm trước mỗi yêu cầu; không đủ thì trả về `insufficient_quota`                             |
| Cửa sổ mô hình miễn phí | 15 tin nhắn mỗi 3 giờ, tính theo tài khoản — ×3 ở Pro (45), ×4 ở Premium (60), ×5 ở Ultimate (75) |

Cửa sổ mô hình miễn phí dùng chung với trò chuyện trên web, truyện, tiểu thuyết và các bot — đó là một phần chung cho cả tài khoản, không phải mỗi nơi một phần. [Mô hình miễn phí →](https://reverie.im/vi/docs/reference/free-models)

Reverie không gửi webhook ra ngoài; không có gì để đăng ký nhận sự kiện sinh xong.

## Nhập nhân vật \[#importing-characters]

Còn một endpoint thứ hai, `POST /api/v1/characters/import`, tạo nhân vật từ một thẻ nhân vật. Nó cần khóa có phạm vi nhập, mà đó không phải loại khóa trang cài đặt tạo ra. [API Keys →](https://reverie.im/vi/docs/integrations/api-keys) và [Nhập nhân vật →](https://reverie.im/vi/docs/characters/import-characters)

## Liên quan \[#related]

* [API Keys](https://reverie.im/vi/docs/integrations/api-keys) — tạo, ghim, đặt hạn và theo dõi khóa
* [Mô hình](https://reverie.im/vi/docs/reference/models) — ID mô hình và hệ số nhân tín dụng
* [Giới hạn](https://reverie.im/vi/docs/reference/limits) — những trần khác trong sản phẩm
* [Tín dụng](https://reverie.im/vi/docs/billing/credits) — yêu cầu được tính giá thế nào
