# Chat completions

`POST /v1/chat/completions` — Run a conversation turn against RP+ or RP mini.

- Base URL: https://eroq.ai/v1
- Auth: `Authorization: Bearer eroq_sk_…` (create keys at https://eroq.ai/dashboard/keys)
- Credits: RP+ 3 · RP mini 1 per completion · +2 per attached image

The request shape follows the OpenAI chat convention: a `model` and a `messages` array of `system` / `user` / `assistant` turns. Existing OpenAI client code usually ports by changing the base URL and the model id.

The API is stateless — send the conversation history you want the model to see on every call. Persona and scene state travel two ways: `system` turns inside `messages` (full control, replaces our preamble), and the top-level `context` field (appended after either), which keeps character sheets and world state out of your transcript management.

**Your users can send pictures.** A user turn's `content` can be an array of parts mixing `{ "type": "text", "text": … }` and `{ "type": "image_url", "image_url": { "url": … } }` (https URL or data URI, up to 2 images per request). The engine looks at the image and the character reacts to it in the reply — +2 credits per image, on top of the completion.

**Two conversation registers.** `mode: "scene"` (default) writes immersive roleplay prose; `mode: "messaging"` answers like texting — short, casual, fast, capped at 160 tokens. Messaging on RP mini is the economical setup for DM-style products: 1 credit, snappy latency.

RP+ and RP mini are uncensored: adult and NSFW roleplay between adult characters renders in character instead of refusing, within the acceptable-use policy (no minors, no real people, nothing illegal).

Set `stream: true` to receive the reply as server-sent events (`data:` chunks, terminated by `data: [DONE]`). Credits are charged per completion, not per token; `max_tokens` is capped at 1200.

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `model` | string | yes | `eroq-rp-plus` or `eroq-rp-mini`. |
| `messages` | array | yes | Conversation turns `{ role, content }` — roles `system`, `user`, `assistant`. `content` is a string, or an array of `text` / `image_url` parts on user turns. |
| `context` | string | no | Persona, scene or lore block, folded into the engine-side prompt after your `system` turns (or after the default preamble). Ideal for character sheets and world state you manage separately from the transcript. |
| `mode` | string | no | `scene` (default, immersive prose) or `messaging` (texting register, short and fast). |
| `stream` | boolean | no | Stream the reply as SSE chunks. Default `false`. |
| `temperature` | number | no | Sampling temperature, `0`–`1.5`. Default `0.8` — tuned where roleplay lives. |
| `max_tokens` | integer | no | Completion budget. Default 500, max 1200 (160 in messaging mode). |

## Example request

```bash
curl https://eroq.ai/v1/chat/completions \
  -H "Authorization: Bearer $EROQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "model": "eroq-rp-plus",
  "context": "Mira: sardonic starship mechanic, dry humor, hates small talk. Scene: engine bay, mid-shift.",
  "messages": [
    {
      "role": "user",
      "content": "The reactor is making that noise again."
    }
  ]
}'
```

```javascript
const res = await fetch('https://eroq.ai/v1/chat/completions', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.EROQ_API_KEY}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "model": "eroq-rp-plus",
    "context": "Mira: sardonic starship mechanic, dry humor, hates small talk. Scene: engine bay, mid-shift.",
    "messages": [
      {
        "role": "user",
        "content": "The reactor is making that noise again."
      }
    ]
  }),
})
console.log(await res.json())
```

```python
import os, requests

res = requests.post(
    "https://eroq.ai/v1/chat/completions",
    headers={"Authorization": f"Bearer {os.environ['EROQ_API_KEY']}"},
    json={
        "model": "eroq-rp-plus",
        "context": "Mira: sardonic starship mechanic, dry humor, hates small talk. Scene: engine bay, mid-shift.",
        "messages": [
            {
                "role": "user",
                "content": "The reactor is making that noise again."
            }
        ]
    },
)
print(res.json())
```

```go
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"strings"
)

func main() {
	body := strings.NewReader(`{
  "model": "eroq-rp-plus",
  "context": "Mira: sardonic starship mechanic, dry humor, hates small talk. Scene: engine bay, mid-shift.",
  "messages": [
    {
      "role": "user",
      "content": "The reactor is making that noise again."
    }
  ]
}`)

	req, _ := http.NewRequest("POST", "https://eroq.ai/v1/chat/completions", body)
	req.Header.Set("Authorization", "Bearer "+os.Getenv("EROQ_API_KEY"))
	req.Header.Set("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	out, _ := io.ReadAll(res.Body)
	fmt.Println(string(out))
}
```

## Response


```json
{
  "id": "cmpl_9f2e17ab",
  "object": "chat.completion",
  "model": "eroq-rp-plus",
  "choices": [{
    "index": 0,
    "message": {
      "role": "assistant",
      "content": "*slides out from under the manifold, wiping grease on her overalls* That noise is the reactor's way of saying you skipped the coolant flush. Again."
    },
    "finish_reason": "stop"
  }],
  "usage": { "credits_spent": 3, "credits_remaining": 997 }
}
```
---
Canonical: https://eroq.ai/docs/chat · Index for agents: https://eroq.ai/llms.txt · OpenAPI: https://eroq.ai/openapi.json
