Developer docs
Chat (LLM)
Streaming chat sessions and stateless completions with GPT, Gemini, Grok and unfiltered Venice models — billed per token.
The same account, key and credits that generate images also power LLM chat. Two shapes:
| Endpoint | Use it when | State |
|---|---|---|
POST / | You want a conversation with memory (history, title, system prompt) | Stored — appears in the user's chat history |
POST / | You bring the whole message list yourself (agents, scripts, MCP) | Nothing stored except the credit ledger |
Both stream over Server-Sent Events when you send Accept: text/event-stream, and return plain JSON otherwise.
Models & pricing
GET /v1/models?modality=chatChat models carry chatPricing — credits per 1 million input / output tokens — and creditsFrom, the price of a typical 1k-in / 500-out turn.
| id | Vendor | Context | Vision | In / 1M tok | Out / 1M tok |
|---|---|---|---|---|---|
gpt-5.5 | OpenAI | 400k | yes | 1,450 | 8,700 |
gpt-5.4-mini | OpenAI | 400k | yes | 220 | 1,310 |
gemini-4-argon | 1M | yes | 730 | 3,630 | |
gemini-3.8-flash | 1M | yes | 280 | 1,370 | |
grok-4.7 | xAI | 256k | yes | 730 | 2,030 |
🔞 | Venice | 32k | — | 60 | 270 |
🔞 | Venice | 32k | — | 150 | 580 |
deepseek-v4-uncensored 🔞 | Venice | 128k | — | 100 | 390 |
Prices are rounded; always read them from /v1/models. Every turn costs at least 1 credit.
How a turn is billed
- Before streaming we estimate the prompt tokens (system prompt + history that fits + your message) and reserve
prompt + max replycredits. Not enough balance →402 INSUFFICIENT_CREDITSand nothing is sent. - When the reply finishes we take the provider's real usage, compute the actual credits and refund the difference.
- If the stream fails on the provider side the whole reservation is refunded. If you stop it (or disconnect) you pay only for the tokens generated so far.
Reasoning models (Gemini, GPT-5.5, Grok) count their hidden thinking tokens as output — a short answer can still use a few hundred output tokens.
Sessions
# create
curl https://api.poremu.ai/v1/chat/sessions \
-H "Authorization: Bearer $POREMU_API_KEY" -H "Content-Type: application/json" \
-d '{"model":"gpt-5.4-mini","systemPrompt":"You are a concise copywriter.","title":"Taglines"}'{ "id": "c0a8…", "model": "gpt-5.4-mini", "title": "Taglines", "systemPrompt": "You are a concise copywriter.", "isNsfw": false, "messageCount": 0, "credits": 0, "lastMessageAt": null, "createdAt": "…" }| Route | Purpose |
|---|---|
GET / | Your sessions, most recent first |
POST / {model, title?, systemPrompt?} | Create |
GET / | Session + last 200 messages (oldest first) |
PATCH / {title?, model?, systemPrompt?} | Rename, switch model, change system prompt |
DELETE / | Soft-delete |
POST / {content, attachments?, model?} | Send a message (streams the reply) |
POST / | Stop a reply that is still streaming |
An untitled session is named after the first message (first 40 characters). Passing model on a message switches the session to that model from then on.
Streaming a reply
curl -N https://api.poremu.ai/v1/chat/sessions/SESSION_ID/messages \
-H "Authorization: Bearer $POREMU_API_KEY" \
-H "Content-Type: application/json" -H "Accept: text/event-stream" \
-d '{"content":"Three taglines for a matte ceramic mug."}'event: start
data: {"type":"start","userMessage":{…},"assistantMessageId":"9f1e…"}
event: delta
data: {"type":"delta","text":"1. "}
event: delta
data: {"type":"delta","text":"Quiet mornings, held."}
event: done
data: {"type":"done","message":{"id":"9f1e…","role":"ASSISTANT","status":"DONE","content":"…","usage":{"inputTokens":48,"outputTokens":31},"credits":1,…},"balance":{"paid":1200,"bonus":60,"total":1260}}Events, in order: one start (the stored user message and the id of the assistant message being written), many delta, then exactly one done or error. A stopped reply still ends with done and message.status === "STOPPED". Errors that happen before anything is sent (bad model, blocked prompt, no credits) come back as a normal JSON error with the right status code, not as an SSE event.
Reading the stream from JavaScript (EventSource cannot POST, so use fetch):
const res = await fetch(`${API}/v1/chat/sessions/${id}/messages`, {
method: 'POST',
headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json', Accept: 'text/event-stream' },
body: JSON.stringify({ content: 'Three taglines for a matte ceramic mug.' }),
});
if (!res.ok) throw await res.json(); // pre-stream errors are JSON
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let i: number;
while ((i = buffer.indexOf('\n\n')) !== -1) { // one SSE frame
const frame = buffer.slice(0, i); buffer = buffer.slice(i + 2);
const data = frame.split('\n').find((l) => l.startsWith('data:'))?.slice(5).trim();
if (!data) continue; // ": ping" keep-alives
const ev = JSON.parse(data);
if (ev.type === 'delta') process.stdout.write(ev.text);
if (ev.type === 'done') console.log('\n', ev.message.credits, 'credits');
if (ev.type === 'error') console.error(ev.code, ev.message);
}
}Without the Accept header (or with ?stream=1 absent) the same request blocks and returns { "userMessage", "message", "balance" } once the reply is complete.
Stopping
curl -X POST https://api.poremu.ai/v1/chat/sessions/SESSION_ID/messages/ASSISTANT_MESSAGE_ID/stop \
-H "Authorization: Bearer $POREMU_API_KEY"Returns the message with status: "STOPPED" and whatever text was generated. Closing the HTTP connection has the same effect.
Vision (image attachments)
Models with capabilities.vision accept up to 4 public image URLs per message — your own CDN outputs, uploads, or any https image:
{ "content": "What is wrong with the lighting in this shot?", "attachments": ["https://cdn.poremu.ai/u/…/0.png"] }Attachments stay with the message, so follow-up questions can refer to the image. Each image is budgeted as roughly 800 prompt tokens.
Context window
We send the system prompt, your new message and as much recent history as fits in about 60 % of the model's context window minus the reply budget. Older turns are dropped silently; the session itself keeps every message.
Stateless completions
curl https://api.poremu.ai/v1/chat/completions \
-H "Authorization: Bearer $POREMU_API_KEY" -H "Content-Type: application/json" \
-d '{
"model": "gemini-3.8-flash",
"messages": [
{"role": "system", "content": "Answer in one sentence."},
{"role": "user", "content": "Why is the sky blue?"}
],
"maxOutputTokens": 200,
"temperature": 0.3
}'{ "id": "a6a5…", "model": "gemini-3.8-flash", "content": "Sunlight scatters off air molecules…", "usage": { "inputTokens": 21, "outputTokens": 47 }, "credits": 1, "finishReason": "stop" }With Accept: text/event-stream you get delta events and a final {"type":"done","completion":{…},"balance":{…}}. temperature is passed through when the vendor allows it (OpenAI reasoning models ignore it). finishReason is stop, length (hit maxOutputTokens), content_filter or error.
Limits & gating
| Limit | Value |
|---|---|
| Streaming replies per account at once | 2 (429 CONCURRENCY_LIMIT) |
| Messages per minute | 60 (429 RATE_LIMITED) |
| Message length | 32,000 characters |
| Attachments | 4 per message, vision models only |
- Unfiltered models (
uncensored: true) require adult verification and the content toggle in Settings — otherwise403 ADULT_VERIFICATION_REQUIRED/403 NSFW_DISABLED. Sessions on those models are flaggedisNsfw. - The content policy applies to every model: prompts describing minors or real-person deepfakes return
422 PROMPT_BLOCKEDbefore anything is sent to a provider.
MCP
Assistants connected over MCP get the same capability as the chat tool (keeps a session_id), plus prompt_improve, which uses gpt-5.4-mini to turn a rough idea into a strong generation prompt.