# Open API

> An OpenAI-compatible endpoint for your characters.

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

Reverie exposes an OpenAI-compatible Chat Completions endpoint. Point any OpenAI client at it, pass a Reverie API key, and use the `model` field to name a **character or scenario** instead of an LLM. Reverie keeps the conversation, the memories and the summaries on its side, so each request only needs your new message.

Any signed-in account can use it. There is no subscription tier and no waitlist — you need an API key, and that's all. [Create one →](https://reverie.im/en/docs/integrations/api-keys)

## Base URL and auth \[#base-url-and-auth]

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

**Settings → More → API Keys** shows the exact base URL in the **API Endpoint** card, with a copy button.

> **This is a server-side API.** Cross-origin browser requests are blocked, so
> a fetch from your own web page will fail regardless of the key. Call it from
> a backend, and keep the key out of client code.

## Quick start \[#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 ?? "");
}
```

**Settings → More → API Keys** also has a request builder that generates these three snippets against one of your real keys and a real character.

## The `model` field names a character \[#the-model-field-names-a-character]

> **A bare character name does not work.** `model: "Luna"` does not look up a
> character called Luna. It fails to match anything, falls through to the
> key's default character or your most recent chat, and you get a reply from
> a different character with no error. Always use the `Name [shortid]` form
> that `GET /models` returns.

| Value of `model`                             | What answers                                                            |
| -------------------------------------------- | ----------------------------------------------------------------------- |
| `Luna [abc12345]`                            | The character or scenario whose ID ends with those 8 characters         |
| A full character or scenario ID              | That character or scenario                                              |
| A Reverie model ID, e.g. `deepseek-v4-flash` | Selects the **LLM**; the character comes from the key or your last chat |
| Omitted                                      | The key's pinned character, otherwise your most recent chat             |

Details that decide whether a short ID resolves:

* The `[...]` must be at the **end** of the string.
* A suffix of 8 characters or fewer is matched only against characters **you already have a chat with**, on the web or through the API. A short ID for a character you have never opened falls through silently.
* The scenario lookup only runs when the key is pinned to a character. With a global key, a short scenario ID can never resolve.
* More than 8 characters is treated as a full ID.

### Listing what you can talk to \[#listing-what-you-can-talk-to]

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

A **global** key returns your recent characters. A key **pinned to a character** returns that character's scenarios instead, or the character itself if it has none. Each entry looks like:

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

Copy `id` straight into `model`. Errors from this endpoint use a plain error shape rather than the OpenAI envelope.

## What the request body reads \[#what-the-request-body-reads]

Four fields are read. Everything else is accepted and silently discarded.

| Field         | Type       | Default                         | Notes                                           |
| ------------- | ---------- | ------------------------------- | ----------------------------------------------- |
| `messages`    | array      | required                        | See below — only part of it is used             |
| `model`       | string     | optional                        | Character, scenario or LLM, as above            |
| `stream`      | boolean    | **`true`**                      | Streaming is the default, unlike the OpenAI API |
| `temperature` | number 0–2 | character's setting, else `0.8` | Out-of-range values are rejected                |

Ignored without warning: `max_tokens`, `top_p`, `n`, `stop`, `presence_penalty`, `frequency_penalty`, `logit_bias`, `seed`, `user`, `response_format`, `tools`, `tool_choice`, `logprobs`, `stream_options`.

How `messages` is handled:

* **`system` messages are dropped.** Persona and behaviour come from the character's own fields, not from the request. [Prompt fields →](https://reverie.im/en/docs/characters/prompt-fields)
* **There is no vision support.** Content may be a string or a parts array, but only `text` parts are read; image parts are ignored.
* Only the **last `user` message** is added to the conversation. Earlier user messages are ignored.
* Assistant messages are used only for history sync (below).
* An empty or whitespace-only last user message returns `missing_user_message`.

## Responses \[#responses]

A non-streaming response is a normal `chat.completion` object. `finish_reason` is always `stop` — internal tool calls are never exposed — and the `usage` counters are zeros, so measure tokens from the usage statistics on the API keys page rather than from the response.

A streaming response is `text/event-stream`:

1. Content chunks: `chat.completion.chunk` objects with `delta.content`. \*&#x2A;No initial `delta: {"role": "assistant"}` chunk is sent.*\*
2. A finish chunk, with `finish_reason` of `stop`, `length` or `content_filter`. Tool-call finishes are reported as `stop`.
3. A usage chunk with an empty `choices` array.
4. `data: [DONE]`.

If generation fails mid-stream, the stream emits `data: {"error": {...}}` and then `[DONE]`.

## Conversation state \[#conversation-state]

Reverie stores the conversation. One chat exists per key-and-character pair, it shows up in the web app alongside your other chats, and each turn is built from the character's system prompt and scenario, world book canon, long-term memories, rolling summaries, the character's narration style, response length and NSFW settings, a language lock from your account locale, and as much of the recent history as the model's context window allows. Memory tools run when the model supports tools.

Because the server holds the history, you do not need to replay it.

> **Sending assistant messages can delete history.** If your `messages` array
> contains assistant messages, Reverie takes the last one, looks for it among
> the 20 most recent stored messages, and — on a match — permanently deletes
> every message created after it, along with memories created after that
> point. If nothing matches, nothing is deleted. The length of your array is
> irrelevant. To avoid this entirely, send only the new user message.

## Which LLM answers \[#which-llm-answers]

The character decides, not the request — unless you name an LLM. Precedence, lowest to highest: the default chat model, then the character's own model setting, then a Reverie model ID passed as `model`, and finally the per-chat model preference you set in the web app, which wins over all of them. [Models →](https://reverie.im/en/docs/reference/models)

## Errors \[#errors]

Errors from `/chat/completions` use the OpenAI envelope: `{"error": {"message", "type", "param": null, "code"}}`.

| HTTP   | `code`                 | `type`                  | Cause                                                                     |
| ------ | ---------------------- | ----------------------- | ------------------------------------------------------------------------- |
| 401    | `invalid_api_key`      | `invalid_request_error` | Missing or malformed `Authorization`, unknown key, or the key is inactive |
| 401    | `api_key_expired`      | `invalid_request_error` | The key is past its expiry                                                |
| 404    | `scenario_not_found`   | `invalid_request_error` | A scenario ID resolved but the scenario is gone                           |
| 404    | `character_not_found`  | `invalid_request_error` | The character is missing or deleted                                       |
| 404    | `no_chat_history`      | `invalid_request_error` | No `model`, no pinned character, and no prior chat to fall back on        |
| 400    | `missing_user_message` | `invalid_request_error` | No non-empty `user` message                                               |
| 429    | `rate_limit_exceeded`  | `rate_limit_exceeded`   | Free-model window exhausted. `Retry-After` gives the seconds to wait      |
| 429    | `insufficient_quota`   | `insufficient_quota`    | Not enough credits for the request                                        |
| 502    | `MODEL_EMPTY_RESPONSE` | `server_error`          | The model finished cleanly with no text                                   |
| varies | provider code          | `server_error`          | Generation failed upstream                                                |

Two cases fall outside that envelope: `GET /models` errors, and request bodies rejected by validation (an out-of-range `temperature`, for example) — both return a plain error shape.

## Rate limits and quotas \[#rate-limits-and-quotas]

There is **no per-key rate limit, no concurrency cap and no request quota** on this API. Two things bound your usage:

| Bound             | Value                                                                                          |
| ----------------- | ---------------------------------------------------------------------------------------------- |
| Credit balance    | Checked before each request; failure returns `insufficient_quota`                              |
| Free model window | 15 messages per 3 hours, per account — ×3 on Pro (45), ×4 on Premium (60), ×5 on Ultimate (75) |

The free-model window is shared with web chat, stories, novels and the bots — it is one allowance per account, not one per surface. [Free models →](https://reverie.im/en/docs/reference/free-models)

Reverie sends no outbound webhooks; there is nothing to subscribe to for completion events.

## Importing characters \[#importing-characters]

A second endpoint, `POST /api/v1/characters/import`, creates a character from a character card. It needs a key with the import scope, which is not the kind of key the settings page creates. [API keys →](https://reverie.im/en/docs/integrations/api-keys) and [Import characters →](https://reverie.im/en/docs/characters/import-characters)

## Related \[#related]

* [API keys](https://reverie.im/en/docs/integrations/api-keys) — create, pin, expire and monitor keys
* [Models](https://reverie.im/en/docs/reference/models) — model IDs and credit multipliers
* [Limits](https://reverie.im/en/docs/reference/limits) — the other ceilings in the product
* [Credits](https://reverie.im/en/docs/billing/credits) — how requests are priced
