# Open API

> Endpoint yang kompatibel dengan OpenAI untuk karakter-karaktermu.

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

Reverie menyediakan endpoint Chat Completions yang kompatibel dengan OpenAI. Arahkan klien OpenAI mana pun ke sana, kirim kunci API Reverie, dan gunakan kolom `model` untuk menyebut sebuah **karakter atau skenario**, bukan sebuah LLM. Percakapan, memori, dan ringkasannya disimpan Reverie di sisinya, jadi tiap permintaan cukup membawa pesan barumu saja.

Akun mana pun yang sudah masuk bisa memakainya. Tidak ada tingkat langganan dan tidak ada daftar tunggu — kamu butuh sebuah kunci API, itu saja. [Buat satu →](https://reverie.im/id/docs/integrations/api-keys)

## URL dasar dan autentikasi \[#base-url-and-auth]

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

**Pengaturan → Lainnya → Kunci API** menampilkan URL dasar yang tepat pada kartu **API Endpoint**, lengkap dengan tombol salin.

> **Ini API sisi server.** Permintaan peramban dari asal lain diblokir, jadi fetch dari halaman webmu sendiri akan gagal, sebenar apa pun kuncinya. Panggil dari backend, dan jauhkan kuncinya dari kode klien.

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

**Pengaturan → Lainnya → Kunci API** juga punya perakit permintaan yang membuat ketiga cuplikan ini memakai salah satu kunci aslimu dan karakter sungguhan.

## Kolom `model` menyebut sebuah karakter \[#the-model-field-names-a-character]

> **Nama karakter saja tidak bekerja.** `model: "Luna"` tidak mencari karakter bernama Luna. Ia tidak cocok dengan apa pun, jatuh ke karakter bawaan milik kunci atau ke obrolan terakhirmu, dan kamu menerima balasan dari karakter lain tanpa pesan kesalahan sama sekali. Selalu pakai bentuk `Nama [idpendek]` yang dikembalikan `GET /models`.

| Nilai `model`                                  | Siapa yang menjawab                                                        |
| ---------------------------------------------- | -------------------------------------------------------------------------- |
| `Luna [abc12345]`                              | Karakter atau skenario yang ID-nya berakhir dengan 8 karakter itu          |
| ID karakter atau skenario yang lengkap         | Karakter atau skenario tersebut                                            |
| ID model Reverie, misalnya `deepseek-v4-flash` | Memilih **LLM**-nya; karakternya datang dari kunci atau obrolan terakhirmu |
| Dikosongkan                                    | Karakter yang disematkan pada kunci, kalau tidak ada, obrolan terakhirmu   |

Hal-hal yang menentukan apakah ID pendek bisa dikenali:

* `[...]` harus berada di **akhir** teks.
* Akhiran sepanjang 8 karakter atau kurang hanya dicocokkan dengan karakter **yang sudah punya obrolan denganmu**, di web maupun lewat API. ID pendek milik karakter yang belum pernah kamu buka akan meleset diam-diam.
* Pencarian skenario hanya berjalan kalau kuncinya disematkan pada sebuah karakter. Dengan kunci global, ID skenario yang pendek tidak akan pernah dikenali.
* Lebih dari 8 karakter diperlakukan sebagai ID lengkap.

### Melihat dengan siapa kamu bisa bicara \[#listing-what-you-can-talk-to]

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

Kunci **global** mengembalikan karakter-karaktermu yang terbaru. Kunci yang **disematkan pada sebuah karakter** malah mengembalikan skenario karakter itu, atau karakternya sendiri kalau ia tak punya skenario. Tiap entri terlihat begini:

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

Salin `id` langsung ke `model`. Kesalahan dari endpoint ini memakai bentuk kesalahan sederhana, bukan bungkus OpenAI.

## Apa yang dibaca dari isi permintaan \[#what-the-request-body-reads]

Empat kolom yang dibaca. Selebihnya diterima lalu dibuang diam-diam.

| Kolom         | Tipe      | Bawaan                                     | Catatan                                                   |
| ------------- | --------- | ------------------------------------------ | --------------------------------------------------------- |
| `messages`    | larik     | wajib                                      | Lihat di bawah — hanya sebagiannya yang dipakai           |
| `model`       | string    | opsional                                   | Karakter, skenario, atau LLM, seperti di atas             |
| `stream`      | boolean   | **`true`**                                 | Streaming adalah perilaku bawaan, berbeda dari API OpenAI |
| `temperature` | angka 0–2 | setelan karakternya, kalau tidak ada `0.8` | Nilai di luar rentang ditolak                             |

Diabaikan tanpa peringatan: `max_tokens`, `top_p`, `n`, `stop`, `presence_penalty`, `frequency_penalty`, `logit_bias`, `seed`, `user`, `response_format`, `tools`, `tool_choice`, `logprobs`, `stream_options`.

Bagaimana `messages` diperlakukan:

* **Pesan `system` dibuang.** Kepribadian dan perilaku datang dari kolom-kolom karakternya sendiri, bukan dari permintaan. [Kolom prompt →](https://reverie.im/id/docs/characters/prompt-fields)
* **Tidak ada dukungan penglihatan.** Isinya boleh berupa string atau larik bagian, tetapi hanya bagian `text` yang dibaca; bagian gambar diabaikan.
* Yang ditambahkan ke percakapan hanya **pesan `user` terakhir**. Pesan pengguna sebelumnya diabaikan.
* Pesan asisten hanya dipakai untuk menyelaraskan riwayat (lihat di bawah).
* Pesan pengguna terakhir yang kosong atau hanya berisi spasi akan mengembalikan `missing_user_message`.

## Respons \[#responses]

Respons tanpa streaming adalah objek `chat.completion` biasa. `finish_reason` selalu `stop` — panggilan perkakas internal tidak pernah ditampakkan — dan penghitung `usage` semuanya nol, jadi ukurlah token dari statistik pemakaian di halaman kunci API, bukan dari responsnya.

Respons streaming berupa `text/event-stream`:

1. Potongan isi: objek `chat.completion.chunk` dengan `delta.content`. **Tidak ada potongan pembuka `delta: {"role": "assistant"}` yang dikirim.**
2. Potongan penutup, dengan `finish_reason` berupa `stop`, `length`, atau `content_filter`. Penutupan karena panggilan perkakas dilaporkan sebagai `stop`.
3. Potongan pemakaian dengan larik `choices` yang kosong.
4. `data: [DONE]`.

Kalau pembuatan gagal di tengah aliran, aliran itu mengeluarkan `data: {"error": {...}}` lalu `[DONE]`.

## Keadaan percakapan \[#conversation-state]

Reverie menyimpan percakapannya. Ada satu obrolan untuk tiap pasangan kunci dan karakter, obrolan itu muncul di aplikasi web bersama obrolanmu yang lain, dan tiap giliran disusun dari prompt sistem serta skenario karakternya, kanon buku dunia, memori jangka panjang, ringkasan yang terus diperbarui, gaya penceritaan karakter, panjang balasan dan setelan NSFW, kunci bahasa dari bahasa akunmu, dan sebanyak mungkin riwayat terbaru yang muat di jendela konteks modelnya. Perkakas memori berjalan bila modelnya mendukung perkakas.

Karena riwayatnya dipegang server, kamu tidak perlu mengirimnya ulang.

> **Mengirim pesan asisten bisa menghapus riwayat.** Kalau larik `messages`-mu berisi pesan asisten, Reverie mengambil yang terakhir, mencarinya di antara 20 pesan tersimpan yang paling baru, dan begitu cocok, ia menghapus permanen setiap pesan yang dibuat setelahnya, berikut memori yang lahir setelah titik itu. Kalau tidak ada yang cocok, tidak ada yang dihapus. Panjang larikmu tidak berpengaruh. Untuk menghindarinya sama sekali, kirim saja pesan pengguna yang baru.

## LLM mana yang menjawab \[#which-llm-answers]

Karakternya yang memutuskan, bukan permintaanmu — kecuali kamu menyebut sebuah LLM. Urutan dari yang terlemah ke terkuat: model obrolan bawaan, lalu setelan model milik karakternya, lalu ID model Reverie yang dikirim sebagai `model`, dan terakhir preferensi model yang kamu tetapkan untuk obrolan itu di aplikasi web, yang mengalahkan semuanya. [Model →](https://reverie.im/id/docs/reference/models)

## Kesalahan \[#errors]

Kesalahan dari `/chat/completions` memakai bungkus OpenAI: `{"error": {"message", "type", "param": null, "code"}}`.

| HTTP    | `code`                 | `type`                  | Penyebab                                                                                          |
| ------- | ---------------------- | ----------------------- | ------------------------------------------------------------------------------------------------- |
| 401     | `invalid_api_key`      | `invalid_request_error` | `Authorization` hilang atau salah bentuk, kunci tak dikenal, atau kuncinya nonaktif               |
| 401     | `api_key_expired`      | `invalid_request_error` | Kuncinya sudah lewat masa berlaku                                                                 |
| 404     | `scenario_not_found`   | `invalid_request_error` | ID skenario dikenali, tetapi skenarionya sudah tidak ada                                          |
| 404     | `character_not_found`  | `invalid_request_error` | Karakternya tidak ada atau sudah dihapus                                                          |
| 404     | `no_chat_history`      | `invalid_request_error` | Tidak ada `model`, tidak ada karakter tersemat, dan tidak ada obrolan sebelumnya sebagai cadangan |
| 400     | `missing_user_message` | `invalid_request_error` | Tidak ada pesan `user` yang tidak kosong                                                          |
| 429     | `rate_limit_exceeded`  | `rate_limit_exceeded`   | Jendela model gratis habis. `Retry-After` memberi jumlah detik yang harus ditunggu                |
| 429     | `insufficient_quota`   | `insufficient_quota`    | Kredit tidak cukup untuk permintaan ini                                                           |
| 502     | `MODEL_EMPTY_RESPONSE` | `server_error`          | Modelnya selesai dengan rapi tetapi tanpa teks                                                    |
| beragam | kode penyedia          | `server_error`          | Pembuatan gagal di hulu                                                                           |

Dua hal berada di luar bungkus itu: kesalahan `GET /models`, dan isi permintaan yang ditolak oleh pemeriksaan (misalnya `temperature` di luar rentang) — keduanya mengembalikan bentuk kesalahan sederhana.

## Batas laju dan kuota \[#rate-limits-and-quotas]

API ini **tidak punya batas laju per kunci, tidak punya batas permintaan serentak, dan tidak punya kuota permintaan**. Hanya dua hal yang membatasi pemakaianmu:

| Batas                | Nilai                                                                                   |
| -------------------- | --------------------------------------------------------------------------------------- |
| Saldo kredit         | Diperiksa sebelum tiap permintaan; kalau gagal, ia mengembalikan `insufficient_quota`   |
| Jendela model gratis | 15 pesan tiap 3 jam, per akun — ×3 di Pro (45), ×4 di Premium (60), ×5 di Ultimate (75) |

Jendela model gratis dipakai bersama dengan obrolan web, cerita, novel, dan para bot — ia satu jatah per akun, bukan satu jatah untuk tiap tempat. [Model gratis →](https://reverie.im/id/docs/reference/free-models)

Reverie tidak mengirim webhook ke luar; tidak ada yang bisa dilanggani untuk memantau selesainya pembuatan.

## Mengimpor karakter \[#importing-characters]

Ada endpoint kedua, `POST /api/v1/characters/import`, yang membuat karakter dari sebuah kartu karakter. Ia butuh kunci dengan cakupan impor, dan itu bukan jenis kunci yang dibuat halaman pengaturan. [Kunci API →](https://reverie.im/id/docs/integrations/api-keys) dan [Mengimpor karakter →](https://reverie.im/id/docs/characters/import-characters)

## Terkait \[#related]

* [Kunci API](https://reverie.im/id/docs/integrations/api-keys) — membuat, menyematkan, memberi masa berlaku, dan memantau kunci
* [Model](https://reverie.im/id/docs/reference/models) — ID model dan pengali kredit
* [Batasan](https://reverie.im/id/docs/reference/limits) — batas-batas lain di produk ini
* [Kredit](https://reverie.im/id/docs/billing/credits) — bagaimana permintaan dihargai
