Skip to content

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:

EndpointUse it whenState
POST /v1/chat/sessions/{id}/messagesYou want a conversation with memory (history, title, system prompt)Stored — appears in the user's chat history
POST /v1/chat/completionsYou 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

text
GET /v1/models?modality=chat

Chat models carry chatPricing — credits per 1 million input / output tokens — and creditsFrom, the price of a typical 1k-in / 500-out turn.

idVendorContextVisionIn / 1M tokOut / 1M tok
gpt-5.5OpenAI400kyes1,4508,700
gpt-5.4-miniOpenAI400kyes2201,310
gemini-4-argonGoogle1Myes7303,630
gemini-3.8-flashGoogle1Myes2801,370
grok-4.7xAI256kyes7302,030
venice-uncensored 🔞Venice32k—60270
venice-roleplay 🔞Venice32k—150580
deepseek-v4-uncensored 🔞Venice128k—100390

Prices are rounded; always read them from /v1/models. Every turn costs at least 1 credit.

How a turn is billed

  1. Before streaming we estimate the prompt tokens (system prompt + history that fits + your message) and reserve prompt + max reply credits. Not enough balance → 402 INSUFFICIENT_CREDITS and nothing is sent.
  2. When the reply finishes we take the provider's real usage, compute the actual credits and refund the difference.
  3. 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

bash
# 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"}'
json
{ "id": "c0a8…", "model": "gpt-5.4-mini", "title": "Taglines", "systemPrompt": "You are a concise copywriter.", "isNsfw": false, "messageCount": 0, "credits": 0, "lastMessageAt": null, "createdAt": "…" }
RoutePurpose
GET /v1/chat/sessions?cursor&limitYour sessions, most recent first
POST /v1/chat/sessions {model, title?, systemPrompt?}Create
GET /v1/chat/sessions/{id}Session + last 200 messages (oldest first)
PATCH /v1/chat/sessions/{id} {title?, model?, systemPrompt?}Rename, switch model, change system prompt
DELETE /v1/chat/sessions/{id}Soft-delete
POST /v1/chat/sessions/{id}/messages {content, attachments?, model?}Send a message (streams the reply)
POST /v1/chat/sessions/{id}/messages/{messageId}/stopStop 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

bash
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."}'
text
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):

ts
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

bash
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:

json
{ "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

bash
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
  }'
json
{ "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

LimitValue
Streaming replies per account at once2 (429 CONCURRENCY_LIMIT)
Messages per minute60 (429 RATE_LIMITED)
Message length32,000 characters
Attachments4 per message, vision models only
  • Unfiltered models (uncensored: true) require adult verification and the content toggle in Settings — otherwise 403 ADULT_VERIFICATION_REQUIRED / 403 NSFW_DISABLED. Sessions on those models are flagged isNsfw.
  • The content policy applies to every model: prompts describing minors or real-person deepfakes return 422 PROMPT_BLOCKED before 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.