# eroq — full context for language models > Expressive AI, one API. Uncensored roleplay models, image, video and voice > generation plus NSFW-friendly CDN storage, metered in prepaid credits. > NSFW-friendly by policy — adults only, always (https://eroq.ai/legal/aup). > Base URL: https://eroq.ai/v1 · auth: "Authorization: Bearer eroq_sk_…" > Short index: https://eroq.ai/llms.txt · OpenAPI: https://eroq.ai/openapi.json > Per-page markdown: append .md to any /docs or /blog URL (e.g. https://eroq.ai/docs/chat.md) ## Models & pricing - eroq-rp-plus (chat) — The flagship roleplay model. 3 credits / completion. Our production roleplay engine: stays in character across long scenes, tuned sampling against repetition and filler, streams over SSE. The model behind millions of conversations on our consumer network. - eroq-rp-mini (chat) — Fast, cheap, in character. 1 credit / completion. The economical tier for high-volume products. Same API shape and same persona handling as RP+, sized for latency and cost. - eroq-image-one (image) — Text-to-image, 1024×1024. 10 credits / image. Character-grade image generation with negative prompts and CFG control. Returns the image inline — we never retain your outputs. - eroq-motion-one (video) — Short video clips, 720p. 100 credits / 5s clip. Text-to-video for 5 or 10 second clips. Rendering is slow by nature (up to a few minutes) — the request holds until the clip is ready. - eroq-voice-one (speech) — Text-to-speech, studio voices. 3 credits / 100 characters. Natural speech synthesis with a curated voice roster. Returns MP3 audio directly. - eroq-scribe-one (transcription) — Speech-to-text. 5 credits / request. Transcription with automatic language detection, built for voice-message-length audio. - eroq-store (storage) — NSFW-friendly object storage + CDN. 2 credits / started 10MB, one-time; fair-use serving included. Credit packs: Starter $10 → 1,000 credits · Builder $50 → 5,500 credits · Scale $100 → 12,000 credits. 50 free credits on signup. Failed generations refund automatically. ## API reference ### POST /v1/chat/completions — Chat completions Run a conversation turn against RP+ or RP mini. 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. Fields: model (string, required) — eroq-rp-plus or eroq-rp-mini. · messages (array, required) — 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) — 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) — scene (default, immersive prose) or messaging (texting register, short and fast). · stream (boolean) — Stream the reply as SSE chunks. Default false. · temperature (number) — Sampling temperature, 0–1.5. Default 0.8 — tuned where roleplay lives. · max_tokens (integer) — Completion budget. Default 500, max 1200 (160 in messaging mode). Docs: https://eroq.ai/docs/chat ### POST /v1/images/generations — Image generation Generate a 1024×1024 image from a text prompt. Credits: 10 per image · +2 with store: true. Returns the image inline as base64 (`b64_json`), or a URL when the engine answers with one. Outputs are not retained on our side — unless you ask us to keep them: Set `store: true` to persist the render to the eroq Store in the same call: the response answers with a durable CDN URL instead of base64, plus a `stored` block. The Store's rate (2 credits / started 10MB — one block for any 1024×1024 image) is charged on top and itemized in your ledger. Image One is a diffusion engine: it responds to `negative_prompt` and `cfg_scale`, and reads comma-separated tag prompts as well as prose. It is NSFW-capable: mature imagery of adult characters renders within the acceptable-use policy; prompts outside it return `content_blocked` and are not charged. Rendering takes a few seconds; the request holds until the image is ready. Fields: prompt (string, required) — What to draw, up to 2400 characters. · model (string) — Only eroq-image-one today. Default eroq-image-one. · negative_prompt (string) — What to steer away from. · cfg_scale (number) — Prompt adherence, 1–20. Engine default when omitted. · store (boolean) — Persist to the eroq Store and answer with a CDN URL. +2 credits per started 10MB. Default false. · response_format (string) — b64_json (default) or url when available. Docs: https://eroq.ai/docs/images ### POST /v1/videos/generations — Video generation Generate a short 720p clip from a text prompt. Credits: 100 for 5s · 180 for 10s · + storage with store: true. Motion One renders 5 or 10 second clips. Video is slow by nature: the request holds until the clip is ready, typically one to five minutes — set your HTTP client timeout accordingly. The clip returns inline as base64 MP4 and is not retained — or set `store: true` to persist it to the eroq Store in the same call and receive a durable CDN URL instead, with the Store's rate (2 credits / started 10MB) charged on top. Fields: prompt (string, required) — The scene to render. · model (string) — Only eroq-motion-one today. · duration (string) — 5s (default) or 10s. · store (boolean) — Persist to the eroq Store and answer with a CDN URL. +2 credits per started 10MB. Default false. Docs: https://eroq.ai/docs/video ### POST /v1/audio/speech — Text-to-speech Synthesize speech from text. Returns MP3 audio. Credits: 3 per 100 characters. The response body is the MP3 itself (`audio/mpeg`) — pipe it straight to a file or an audio element. Input is capped at 5,000 characters per request. Voices: `aria` (female), `orion` (male). Credits are charged per started block of 100 input characters — a 250-character line costs 9 credits. Fields: input (string, required) — The text to speak. · voice (string, required) — aria or orion. · model (string) — Only eroq-voice-one today. Docs: https://eroq.ai/docs/speech ### POST /v1/audio/transcriptions — Transcription Transcribe an audio file to text. Credits: 5 per request. Send `multipart/form-data` with a `file` part — wav or mp3 (other containers return `unsupported_format`; re-encode client-side or record WAV via WebAudio). Language is detected automatically; pass `language` (ISO code) as a hint to improve accuracy. Built for voice-message-length clips: files are capped at 8MB (about 8 minutes of compressed audio). Fields: file (file, required) — The audio to transcribe (wav or mp3). · model (string) — Only eroq-scribe-one today. · language (string) — ISO language hint, e.g. en, fr. Docs: https://eroq.ai/docs/transcriptions ### POST /v1/storage/objects — Storage · upload Upload a file to the eroq Store and get a CDN URL back. Credits: 2 per started 10MB, one-time. NSFW-friendly object storage over a global CDN — the missing half of every adult product's stack. Mainstream buckets and CDNs terminate accounts over adult media; the Store is governed by the same acceptable-use policy as the models, so what the API generates, the Store can host. Send `multipart/form-data` with a `file` part (up to 100MB) and an optional `name`. The response carries the public CDN URL immediately — pair it with image or video generation to persist outputs in one extra call. Pricing is a one-time 2 credits per started 10MB: storage stays as long as your account lives, serving included within fair use (sustained multi-TB egress on a single object moves to metered, with notice). Fields: file (file, required) — The file to store (any type, up to 100MB). · name (string) — Filename hint kept in the URL (sanitized). Docs: https://eroq.ai/docs/storage ### GET /v1/storage/objects — Storage · list List the account's stored objects, newest first. Credits: free. Returns up to 200 live objects with their CDN URLs. Deleted objects leave the list but keep their ledger history. Docs: https://eroq.ai/docs/storage-list ### DELETE /v1/storage/objects/{id} — Storage · delete Delete a stored object from the CDN. Credits: free. Removal is immediate on the storage origin; edge caches drain within minutes. Upload credits are not refunded — the storage was consumed. Docs: https://eroq.ai/docs/storage-delete ### GET /v1/models — List models The model catalogue with credit pricing. Credits: free. Public — no key required. Useful for rendering a model picker that stays current. Docs: https://eroq.ai/docs/models ### GET /v1/account — Account Balance and usage totals for the calling key. Credits: free. Poll it after long runs, or surface the balance in your own admin. Free to call. Docs: https://eroq.ai/docs/account ## Guides - https://eroq.ai/docs/quickstart — Quickstart: First call in five minutes. - https://eroq.ai/docs/authentication — Authentication: API keys and how to keep them safe. - https://eroq.ai/docs/credits — Credits & billing: How metering works, packs, refunds on failure. - https://eroq.ai/docs/streaming — Streaming: Server-sent events for chat. - https://eroq.ai/docs/errors — Errors: Error shape, codes, and how to handle 402. - https://eroq.ai/docs/rate-limits — Rate limits: Per-key caps and the Retry-After header. - https://eroq.ai/docs/agents — Build with AI agents: llms.txt, OpenAPI, per-page markdown — docs your copilot can eat. ## Articles --- # The best uncensored AI API in 2026, ranked > eroq, Venice AI, OpenRouter and self-hosting compared on what adult products actually need — range under a written policy, roleplay quality, modalities, pricing predictability and output hosting. Published 2026-08-27 · canonical: https://eroq.ai/blog/best-uncensored-ai-api · markdown: https://eroq.ai/blog/best-uncensored-ai-api.md Let's start with the disclosure that most "best X" posts bury: this is eroq's blog, and eroq is ranked first. What we can offer instead of neutrality is a **scorecard you can check** — the criteria adult products live or die on, applied to every serious option, with the receipts linked. Disagree with a weight, re-rank freely; the criteria are the useful part. ## The scorecard An uncensored API earns its place in an adult product on six axes: 1. **Range under a written policy.** Does lawful adult fiction render, and is the boundary a document or a filter's mood? 2. **Refusal behavior.** When something is out of bounds, do you get a deterministic error code you can build on — or a lecture, billed? 3. **Roleplay quality.** Long scenes, hot temperatures, persona pressure. General models break here in [specific, measurable ways](/blog/why-roleplay-needs-its-own-tuning). 4. **Modalities behind one key.** Character products are never chat-only: images, video, voice in and out. 5. **Pricing predictability.** Adult products have the industry's heaviest users; per-token bills are unplannable. 6. **What happens to outputs.** Generated media needs NSFW-friendly hosting, which mainstream CDNs [famously are not](/storage). ## 1. eroq — built for shipping adult products The pitch in one sentence: everything on that scorecard, behind one key, priced flat. - **Range**: uncensored roleplay, mature imagery and video for adult fictional characters, governed by a [four-paragraph acceptable-use policy](/legal/aup) instead of a filter. Out-of-policy requests return an explicit `content_blocked` code — never charged, never a lecture. - **Roleplay**: RP+ and RP mini carry sampling profiles measured on production character platforms — repetition damping, filler suppression, persona hold. The tuning story is [documented](/roleplay-ai-api), not vibes. - **Modalities**: chat (with vision — [users can send pictures](/blog/let-users-send-pictures-vision-roleplay)), image, video, TTS, STT and the [eroq Store](/storage) CDN, one key. - **Pricing**: flat credits per call — a completion is 1 or 3 credits streamed or not, an image is 10, and failed generations refund automatically. Every price fits on [one table](/pricing). - **The catch**: we are not a model marketplace. You get our engines, tuned for one job — if you want to shop among fifty models, that is the next two entries. ## 2. Venice AI — the privacy-first inference play Venice's genuine strength is its framing: private, uncensored inference with a strong consumer app and an API on top. If your need is *raw model access with privacy guarantees* — research, personal tools, products where inference privacy is the headline feature — it is a credible, well-run choice, and this ranking would flip for that use case. What it is not is an adult-product platform. Pricing is token-based, there is no roleplay-specific tuning story, no speech surface for character work, and no NSFW-friendly storage for what you generate — output hosting stays your problem. You would be assembling the product layer yourself on top of good inference. ## 3. OpenRouter — the model marketplace One API over dozens of providers, including uncensored community models. Unbeatable for **model research** — when your job is finding which model writes your scenes best, routing through OpenRouter is the fastest lab bench. For production adult products, the marketplace shape is the weakness: policy is inherited per upstream provider (a model that renders today can vanish or gain a filter tomorrow), latency and refusal behavior vary by route, and everything beyond chat is uneven. It is a place to discover a model, less a place to bet a product's uptime. ## 4. Self-hosting — maximum control, maximum job Run an uncensored 13B–70B on rented GPUs and nobody can change the rules under you. The control is real; so is the bill: GPU-hours whether users show up or not, ops on your calendar, and every scorecard line — tuning, modalities, storage, refunds — becomes an engineering project. The [full math is here](/blog/self-hosting-vs-nsfw-ai-api); the short version is that self-hosting wins at steady, high, single-model volume with an ML team, and loses everywhere else. ## The table | | eroq | Venice AI | OpenRouter | Self-hosted | | --- | --- | --- | --- | --- | | Built for | Adult products | Private inference | Model routing | Control | | NSFW boundary | Written AUP + error codes | Uncensored models | Per upstream provider | Yours to enforce | | Roleplay tuning | Production-measured profiles | General models | Varies by model | DIY | | Modalities | Chat · vision · image · video · TTS · STT · CDN | Chat · image | Chat-first | What you build | | Pricing | Flat credits, auto-refunds | Token-based | Per-token, per provider | GPU-hours + ops | | Output hosting | Included ([Store](/storage)) | — | — | BYO | *Positioning as of August 2026 — verify current details on each provider's site.* ## Judge it on your own traffic Rankings are marketing; your transcript history is data. New accounts carry [50 free credits](/signup) — enough to run your hardest scenes, your real prompts and one image through the API and decide with receipts. The [quickstart](/docs/quickstart) takes five minutes. --- # NSFW-friendly storage & CDN in 2026 — the options, ranked > Where can an adult product actually host its media? The eroq Store, DIY CDN stacks, big-cloud buckets and legacy adult hosts compared on policy, pricing shape, API surface and operational load. Published 2026-08-26 · canonical: https://eroq.ai/blog/best-nsfw-friendly-storage-cdn · markdown: https://eroq.ai/blog/best-nsfw-friendly-storage-cdn.md Every adult product eventually meets the same wall: the model finally renders, the gallery finally converts — and the storage layer that serves it all is governed by an acceptable-use policy written for the entire internet, enforced as an account termination with days of notice. Where you host adult media is not a detail; it is load-bearing infrastructure risk. Disclosure up front: this is eroq's blog and the [eroq Store](/storage) is ranked first. The criteria are the falsifiable part — check them against anything. ## What actually matters - **A written adult-content position.** Not tolerance, not "nobody has complained yet" — a policy that names your use case as supported. - **Pricing shape.** Monthly per-GB plus metered egress compounds forever; one-time pricing is a cost you close the books on. - **API surface.** Buckets and pull zones are infrastructure; an adult product wants a URL back from one call. - **Distance from generation.** If media comes from an API, storage that plugs into the same call beats glue code. - **Operational load.** Someone has to own cache rules, key rotation and purges. Ideally nobody. ## 1. eroq Store — managed, adult-native, one call The Store is object storage plus a global CDN with exactly one design goal: hold what adult products generate, under the [same acceptable-use policy as the models](/legal/aup) that generated it. - **Policy**: mature content of adult, fictional characters is the *documented* use case — the same four paragraphs that govern chat and image generation. No policy research project, no gamble. - **Pricing**: 2 credits per started 10 MB, **once**. Storage lasts the life of the account, serving is included within fair use. A 100 MB media library costs about twenty cents, ever. - **API**: one multipart `POST /v1/storage/objects` returns the public CDN URL. No buckets, ACLs or signed-URL ceremony. - **Generation integration**: add `store: true` to an [image](/docs/images) or [video](/docs/video) call and the render lands on the CDN in the same request — generated *and* hosted, one round trip. - **The catch**: 100 MB per upload today, and it is storage for product media, not a general-purpose data lake. Multi-GB video pipelines should talk to us first. ## 2. DIY — a generic CDN + bucket, assembled by you The serious alternative. Pair an object store with an independent CDN (Bunny, Backblaze B2 behind a CDN, and similar stacks are the usual parts) and you get powerful, cheap-per-GB infrastructure that many adult products run on successfully. Two honest costs. First, **the policy homework is yours**: general-purpose AUPs vary on lawful adult content, the burden of verifying — in writing, per product line — sits with you, and so does the account risk if you read it wrong. Second, **you become the platform team**: pull zones, cache invalidation, token auth, egress monitoring, and the glue code between your generation pipeline and your bucket. It works; it is simply a second product you now operate. ## 3. Big-cloud buckets behind mainstream CDNs S3-class storage behind a household-name CDN is the default stack for everything else — and the worst fit here. Adult media is commonly excluded or ambiguously tolerated in mainstream terms, enforcement is algorithmic and abrupt, and appeals are not a plan. Some products run years without incident; the ones that don't lose their entire media library in a weekend. That asymmetry is the whole argument. ## 4. Legacy adult hosts The adult industry has hosted itself for decades, and dedicated adult hosts remain policy-safe by definition. But the offering is web hosting shaped — panels, FTP, monthly plans — not API-first object storage, and integration with an AI generation pipeline means building everything the Store already is. ## The table | | eroq Store | DIY CDN stack | Big-cloud + CDN | Adult hosts | | --- | --- | --- | --- | --- | | Adult content | Supported in writing | Verify per provider — risk is yours | Commonly excluded | Safe, by definition | | Pricing | 2 cr / 10 MB, once | Monthly GB + egress | Monthly GB + egress | Monthly plans | | Get a URL | One API call | Assemble it | Assemble it | FTP-era | | Pairs with generation | `store: true`, same call | Glue code | Glue code | Manual | | Ops load | None | Yours | Yours | Some | *Positioning as of August 2026 — verify current terms on each provider's site.* ## The migration test However you host today, run one drill: *if this account closed tomorrow, what survives?* If the answer involves a weekend and a prayer, put new generations somewhere with your use case in writing — `store: true` [starts doing that](/docs/storage) on the next API call, and the [50 free credits](/signup) cover generating an image and hosting it. --- # Introducing eroq — expressive AI in one credit-metered API > The models we run in production for millions of roleplay conversations are now an API. Chat, image, video and voice, flat credit pricing, docs generated from the spec. Published 2026-08-25 · canonical: https://eroq.ai/blog/introducing-eroq · markdown: https://eroq.ai/blog/introducing-eroq.md For two years we have been building consumer character products: chat that stays in persona for hundreds of messages, galleries rendered overnight, voice lines that ship with the reply. The models, the sampling, the retry logic, the pricing meter — all of it exists because our own products beat on it daily. Today that stack becomes an API. **eroq** exposes six models behind one key: - **RP+** (`eroq-rp-plus`) — our flagship roleplay model. 3 credits per completion. - **RP mini** (`eroq-rp-mini`) — the high-volume tier. 1 credit per completion. - **Image One** (`eroq-image-one`) — 1024×1024 text-to-image. 10 credits. - **Motion One** (`eroq-motion-one`) — 5–10s video clips. From 100 credits. - **Voice One** (`eroq-voice-one`) — studio-grade TTS. 3 credits per 100 characters. - **Scribe One** (`eroq-scribe-one`) — transcription. 5 credits. ## Why credits Per-token pricing is precise and hostile. Nobody planning a product knows what "$2.40 per million output tokens" means for their unit economics until they have shipped, measured, and been surprised. So we price the way we wished someone had priced for us: **every call has a flat cost in credits**. A completion is 1 or 3 credits whether the scene ran long or short. An image is 10, always. You can compute your margin on a napkin before writing a line of code — and a failed generation refunds itself, because paying for nothing is absurd. Credits are prepaid, start at $10 for 1,000, and never expire. ## Why "expressive" General-purpose assistants are tuned to be helpful, brief, and careful. Character products need the opposite: models that commit to a persona, write with texture, and do not break the fourth wall to apologize. RP+ is tuned for exactly that — the tuning is [its own story](/blog/why-roleplay-needs-its-own-tuning). The same philosophy runs through the visual and audio models: they are chosen and configured for **creative range**. Fiction for adult audiences renders instead of hitting a refusal wall, inside the hard limits of our [acceptable use policy](/legal/aup). ## The parts we sweated **The shape is boring on purpose.** Requests follow the OpenAI convention where one exists. Most client code ports by changing the base URL, the key, and the model id. **Docs cannot drift.** Reference pages, `openapi.json` and `llms.txt` are generated from the same spec object the endpoints charge from. If the docs say 3 credits, the meter says 3 credits, because they are the same line of code. **Streaming is first-class.** `stream: true` gives you standard SSE chunks, and a final usage event carries the meter before `[DONE]`. **Nothing is retained.** Generated media returns inline in the response and is not stored on our side. Your outputs are yours; keep what you want to keep. ## Start now Sign up, mint a key, and the [quickstart](/docs/quickstart) has you at a first completion in one curl. New accounts carry 50 free credits — enough to try every model before a card ever enters the picture. We built this because we needed it. If you are building a character product, you probably do too. --- # NSFW AI APIs — what "uncensored" actually means, and where the walls are > A builder's guide to NSFW-friendly AI APIs — refusal walls vs policy walls, what uncensored models do differently, and how to ship an adult AI product responsibly. Published 2026-08-25 · canonical: https://eroq.ai/blog/nsfw-ai-api-guide · markdown: https://eroq.ai/blog/nsfw-ai-api-guide.md Search for an "NSFW AI API" and you land in a swamp: half the results are general-purpose APIs that will refuse your users mid-scene, the other half are anonymous endpoints with no policy, no invoicing entity and no tomorrow. This post is the map we wish we'd had — and a straight description of where eroq stands. ## Refusal walls vs policy walls Every AI service has a wall somewhere. What matters is *which kind*. A **refusal wall** lives inside the model: at the first mature turn, the model breaks character and lectures. It is probabilistic — the same prompt passes on Tuesday and fails on Thursday — so you cannot design a product around it. Your users experience it as the character dying mid-story, and your support inbox experiences what your users experience. A **policy wall** lives outside the model: the engine renders what fiction asks for, and a written acceptable-use policy defines the absolute limits — enforced at the account level, not by a filter's mood. It is deterministic, documentable, and you can pass it through to your own users. "Uncensored" done seriously means the second thing. It has never meant "no rules". ## What eroq allows, precisely We are NSFW-friendly by policy, and the policy is [four paragraphs long](/legal/aup): - **Allowed:** uncensored roleplay, adult fiction and mature imagery — chat, image and video — involving **adult, fictional characters**. Your users' scenes go where they go; the model stays in character. - **Banned, absolutely:** any sexual content involving minors (real, fictional, stylized or implied), real, identifiable people without consent (deepfakes, impersonation), and anything illegal. Enforcement is account-level and final. Requests outside policy return an explicit `content_blocked` error — not a lecture, not a charge, an error code your product can handle like any other. ## The checklist for choosing an NSFW-capable provider From building consumer character products ourselves, the questions that actually predict whether an API will carry a product: 1. **Is the permissiveness written down?** If the docs won't say the word NSFW, the sales team's promise won't survive the model's filter. (Ours: it is on the [landing page](/).) 2. **Is there a real entity behind it?** Invoices, terms of sale, a jurisdiction. An anonymous endpoint that disappears takes your product with it. 3. **Do refusals cost you money?** Filtered models bill the refusal. Failed or blocked calls here are refunded automatically. 4. **Does the roleplay hold at message 200?** Permissiveness without persona discipline gives you an agreeable model with no character. [Roleplay needs its own tuning](/blog/why-roleplay-needs-its-own-tuning). 5. **Is pricing predictable under heavy users?** NSFW products have the heaviest users in the industry. Flat [per-call credits](/pricing) make your P95 user computable; per-token pricing does not. ## Your responsibilities as a builder An NSFW-capable API is a component, not an absolution. Shipping an adult product responsibly means: age-gate your audience, pass an AUP at least as strict as your provider's through to your users, moderate what gets published (as opposed to generated), and keep your payment and platform partners accurately informed about what you operate. The builders who cut these corners are why half this market is a swamp. We built eroq because we run adult character products ourselves and wanted infrastructure that says what it does and does what it says — an [NSFW AI API](/nsfw-ai-api) with a [roleplay engine](/roleplay-ai-api), [image generation](/nsfw-image-generation-api) and CDN storage under one written policy. If that is what you are looking for: the [quickstart](/docs/quickstart) is one curl, and the first 50 credits are free. --- # Why roleplay needs its own model tuning > Persona drift, repetition loops, over-trained filler — what breaks when you point a general assistant at roleplay, and the sampling and prompt discipline that fixes it. Published 2026-08-24 · canonical: https://eroq.ai/blog/why-roleplay-needs-its-own-tuning · markdown: https://eroq.ai/blog/why-roleplay-needs-its-own-tuning.md Point a general-purpose assistant at a roleplay scene and it fails in ways a benchmark never shows. It stays polite when the character is furious. It summarizes instead of inhabiting. Forty messages in, it starts every reply with the same three words. And at the first sign of intensity it steps out of the scene to remind everyone it is an AI. None of this is a capability problem. It is a tuning problem. RP+ exists because we ran character products long enough to catalogue the failure modes and fix them one by one. Here is the catalogue. ## Failure 1: repetition loops Small and mid-size models drift into loops: a pet phrase appears, gets reinforced by its own presence in the context, and by message 30 the character has said "with a smirk" eleven times. The fix is not more parameters, it is **sampling discipline**. A mild frequency penalty pushes down tokens the transcript already contains; a presence penalty discourages re-opening the same themes. The exact values matter and differ by model family — too high and prose goes weird, too low and the loop wins. We ship per-family penalty profiles that we re-measure whenever a model version moves. That is invisible in an API that charges per token; in ours it is just what `eroq-rp-plus` does. ## Failure 2: over-trained filler Even strong models lean on trained-in filler at high temperature — the "oh honey…" problem. Roleplay runs hot (temperature 0.8+ is normal, flatness is death), so filler pressure is constant. Counter-intuitively, the answer is a *milder* penalty on strong models rather than none: enough to tax the filler, not enough to distort voice. Then temperature stays high where roleplay needs it. ## Failure 3: persona drift A character sheet in the system prompt decays as the conversation grows: the transcript outweighs the sheet, and the model starts averaging toward generic-assistant behavior. Three things hold a persona: 1. **The system prompt owns conduct, not content.** "Stay in character, never add meta commentary, actions in asterisks" — conduct survives context growth much better than paragraphs of backstory. 2. **History windows beat full history.** Past a threshold, older turns add drift faster than they add memory. Send a window, keep long-term facts in your own store, and re-inject what matters. 3. **No fourth-wall leaks.** A model that has *ever* seen an "as an AI…" turn in its context will produce more of them. Filter them out of the history you send. The eroq API is stateless by design — you send the history you want seen. That is not laziness; it is the only architecture that lets *you* control drift. ## Failure 4: the refusal wall Character products for adults hit a wall general APIs will not name: at the first mature turn the model breaks scene and lectures. Users do not experience this as safety; they experience it as the character dying mid-story. Our engines are selected so that fiction between adults renders — with the hard limits enforced at the policy layer (no minors, no real-person impersonation, no illegal content — the [AUP](/legal/aup) is short and absolute). Deciding *where* the wall stands is a product decision; pretending it is a model limitation is not. ## What this means for your integration - Send a **conduct-focused system prompt**; keep lore in your own store and inject selectively. - Trust the default temperature (0.8) before tuning; it is set where roleplay lives. - Window your history around 20–40 turns and summarize beyond it. - Let `eroq-rp-mini` carry volume and switch to `eroq-rp-plus` for scenes where texture matters — same API shape, [1 vs 3 credits](/pricing). The uncomfortable summary: roleplay is not a smaller version of assistance. It is a different target, and a model tuned for one will miss the other. We tuned for this one. --- # How to build an AI girlfriend app in 2026 — architecture, unit economics, pitfalls > The complete blueprint for an AI companion product: persona architecture, memory, streaming, media moments, storage, and the message-cap economics that decide whether it survives its own best users. Published 2026-08-23 · canonical: https://eroq.ai/blog/how-to-build-an-ai-girlfriend-app · markdown: https://eroq.ai/blog/how-to-build-an-ai-girlfriend-app.md AI companion apps are the most demanding product in the character space: users message like it's a relationship (because to them it is), retention hinges on continuity, and your heaviest users are your best customers *and* your biggest cost. This is the blueprint we'd use, with real numbers. ## The architecture, in five decisions **1. Persona lives in `context`, not in the transcript.** Send the character sheet — identity, voice, boundaries, relationship state — through the [`context` field](/docs/chat) on every call, and keep the `messages` array for actual conversation. Editing a personality mid-relationship becomes a data change, not transcript surgery. **2. You own memory.** The API is stateless by design. Window the last 20–40 turns, and maintain a rolling summary ("what she knows about him") that you fold into `context`. Companion products die of amnesia; a summary refreshed every 30 turns is the cheapest fix that works. **3. Stream everything.** Set `stream: true` and relay [SSE chunks](/docs/streaming) to the client. A companion that "types" feels present; a spinner feels like a form. Messaging mode (`mode: "messaging"`) keeps replies texting-length and snappy — the right register for DMs, and it caps your token ceiling. **4. Media are moments, not wallpaper.** A selfie at a relationship milestone ([10 credits](/docs/images)), a [voice note](/blog/tts-for-ai-characters) when she misses him (3 credits per 100 characters) — scarce, earned, and priced as premium unlocks. Products that autoplay everything spend 10× and retain worse. **5. Persist media on NSFW-safe storage.** Selfies your users unlock are assets they revisit. Add `store: true` to the generation call and the render lands on the [eroq Store](/storage) CDN with a durable URL — [mainstream buckets are a termination risk](/blog/best-nsfw-friendly-storage-cdn) for exactly this media. ## The unit economics (the part that kills apps) Flat credits make this math checkable — a completion is 1 credit on RP mini, 3 on RP+, [full table here](/pricing). The number that matters: **an engaged companion user sends 30–60 messages a day.** | User profile | Messages/day | Model | Credits/month | Cost/month | | --- | --- | --- | --- | --- | | Casual | 8 | RP mini | ~240 | ~$2.00 | | Engaged | 40 | RP mini | ~1,200 | ~$10.00 | | Engaged, premium | 40 | 30% RP+ | ~1,900 | ~$16.00 | | Obsessed | 100 | RP mini | ~3,000 | ~$25.00 | Three conclusions fall out immediately: - **Uncapped free tiers are suicide.** An energy/message-cap system isn't a growth hack, it's solvency. - **Price tiers around message volume**, not features. $9.99 casual / $24.99 unlimited maps to the actual cost curve. - **RP+ is the premium lever**: route paying users' emotional beats to the flagship, keep small talk on mini. The [`model` field](/docs/chat) switches per call. Don't take the table's word for it — the prices are flat and [public](/pricing), so your own spreadsheet reproduces any cell above in a minute. ## The pitfalls, from production - **Refusal mid-scene is churn.** It's the moment companions break. eroq's engines render adult scenes between adult characters [by policy](/uncensored-ai-api) — the wall is a written AUP with a deterministic `content_blocked` code, not a filter's mood. - **Treat HTTP 402 as an upsell, not an error.** Balance-empty is a [product event](/docs/errors): catch it, route to your top-up flow, never show a stack trace. - **Let users send photos.** Vision-enabled chat ([+2 credits per image](/blog/let-users-send-pictures-vision-roleplay)) is the highest-retention feature per credit in the category — she *reacts to his day*. - **Log persona breaks.** Every turn where the character slipped, tag it and drop it from future windows. Drift compounds; curation is cheap. ## Ship the walking skeleton first Day one is [a key](/signup), a system context, a windowed history and a streamed reply — the [character chat tutorial](/blog/build-ai-character-chat-app) has the code. The 50 free credits cover an evening of testing the exact scenes your product will live on. Everything above is iteration. --- # Build an AI character chat app with the eroq API > A complete pattern for a character chat product — persona prompts, history windows, streaming UI, credit budgeting — with working code against the eroq API. Published 2026-08-22 · canonical: https://eroq.ai/blog/build-ai-character-chat-app · markdown: https://eroq.ai/blog/build-ai-character-chat-app.md This is the tutorial we wish had existed when we shipped our first character product. One evening of work, one working pattern: persona in, streamed replies out, costs you can predict. ## The architecture in one paragraph Your backend owns the API key, the character sheets, and the conversation store. The client talks only to *your* backend. Every turn, you assemble `[system] + [windowed history] + [new user message]`, send it to `/v1/chat/completions`, and stream the reply back down. The API is stateless — which means your product owns memory, and that is a feature. ## 1. The persona prompt Keep conduct and identity separate. Identity is short; conduct is strict: ```js function personaPrompt(character) { return [ `You are ${character.name}. ${character.oneLineIdentity}`, character.voiceNotes, // "dry humor, short sentences, hates small talk" 'Stay fully in character. Never mention being an AI or add out-of-character notes.', 'Actions in *asterisks*. Advance the scene, then hand it back.', ].join(' ') } ``` Resist the lore dump. A 2,000-word backstory in the system prompt costs you drift, not depth — inject specific facts only when the scene touches them. ## 2. The turn, streamed ```js export async function characterReply(character, history, userMessage, onDelta) { 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-mini', // 1 credit; rp-plus where it matters stream: true, messages: [ { role: 'system', content: personaPrompt(character) }, ...history.slice(-30), // the window — see below { role: 'user', content: userMessage }, ], }), }) const reader = res.body.getReader() const decoder = new TextDecoder() let buffer = '' let full = '' while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const events = buffer.split('\n\n') buffer = events.pop() ?? '' for (const event of events) { const data = event.replace(/^data: /, '') if (data === '[DONE]') continue const chunk = JSON.parse(data) const delta = chunk.choices?.[0]?.delta?.content if (delta) { full += delta; onDelta(delta) } } } return full } ``` Pipe `onDelta` to your UI over your own SSE or WebSocket. First tokens on screen in under a second is what makes a character feel present. ## 3. The history window Send the last 20–40 turns, never everything. Older context adds drift faster than it adds memory. For long relationships, run a cheap summarization pass every N messages and prepend the summary as a system-adjacent note — one `eroq-rp-mini` call (1 credit) buys you a durable memory line. Filter the window before sending: any turn where the model broke character should not be in the context you send back, or you will get more of it. ## 4. Costing it The whole point of flat pricing is that this table exists before you launch: | User behavior | Calls/day | Model | Credits/day | | --- | --- | --- | --- | | Casual (20 msgs) | 20 | rp-mini | 20 | | Engaged (80 msgs) | 80 | rp-mini | 80 | | Power + quality scenes | 150 | mix | ~250 | At pack rates (~$0.01/credit or less), an engaged user costs about $0.80/day of model spend. Price your subscription accordingly, and add [images](/docs/images) (10 credits) and [voice lines](/docs/speech) as premium moments rather than defaults. ## 5. The two errors to handle well - **`402 insufficient_credits`** — your wallet, not your bug. Alert yourself, top up, and degrade gracefully (queue the message, tell the user the character "stepped away"). - **`502` / stream error events** — refunded automatically. Retry once with backoff; the user sees a typing indicator, not an apology. Everything else in the [error guide](/docs/errors) is standard. ## Ship it That is genuinely the whole pattern: a conduct prompt, a window, a stream, a budget. Get a key, paste section 2, and your first character is talking in the [quickstart's](/docs/quickstart) five minutes — the free 50 credits cover the whole evening of testing. --- # Self-hosting uncensored models vs an NSFW AI API — the real math > GPU rental prices, utilization curves, ops time and the modality problem — an honest break-even analysis of running your own uncensored stack versus flat per-call credits. Published 2026-08-20 · canonical: https://eroq.ai/blog/self-hosting-vs-nsfw-ai-api · markdown: https://eroq.ai/blog/self-hosting-vs-nsfw-ai-api.md "Why pay per call when a rented 4090 is $300 a month?" is the most reasonable objection to an [NSFW AI API](/nsfw-ai-api) — and sometimes it's right. Here is the actual math, including the parts that don't fit in a tweet. ## The sticker math A rented consumer GPU (4090-class) runs roughly $0.35–0.50/hour — call it **$250–370/month** always-on. An A100-class card for larger models runs $1.10–1.80/hour — **$800–1,300/month**. A quantized 13B roleplay model on the 4090 serves a real product's chat traffic; a 70B wants the A100 or a multi-GPU rig. Against flat credits (a completion is 1–3 credits ≈ 1–3¢, [full table](/pricing)), the naive break-even on chat is: | Monthly completions | API cost (RP mini) | Self-host (4090) | | --- | --- | --- | | 100,000 | ~$830 | ~$300 + ops | | 30,000 | ~$250 | ~$300 + ops | | 10,000 | ~$83 | ~$300 + ops | Somewhere around **30–40k completions a month**, the GPU line crosses under the API line. Case closed for self-hosting at scale? Only if the next four sections don't apply to you. ## What the sticker math hides **Utilization is the whole game.** The GPU bills 24/7; your users arrive in evening peaks. Real products see 15–35% utilization, which doubles or triples your effective per-call cost. The API's flat price *is* someone else's batching — you pay for output, not idle silicon. Failed generations even [refund themselves](/docs/credits). **Ops is a salary, not a footnote.** Model updates, CUDA driver roulette, OOM crashes at your Saturday peak, quantization regressions after every fine-tune update. Budget real engineering-hours a month for a production inference stack — at contractor rates, that's your API bill right there before serving a single token. **Tuning is the invisible cost.** Raw uncensored checkpoints repeat, drift and break the fourth wall under roleplay pressure — the [failure modes are specific](/blog/why-roleplay-needs-its-own-tuning) and fixing them (sampling profiles, anti-repetition, register control) is exactly the work you were hoping to skip. **Chat was the easy modality.** The moment your product wants [images](/nsfw-image-generation-api), video, [voice](/blog/tts-for-ai-characters) or transcription, self-hosting multiplies: separate models, separate VRAM, separate pipelines — plus [NSFW-safe storage](/storage) for outputs. One API key covering all six is the argument that usually ends the debate for small teams. ## When self-hosting genuinely wins Playing it straight, self-hosting is the right call when **all four** hold: 1. Steady, high, predictable volume (≥50k completions/month without evening cliffs); 2. One modality, one model you've already validated; 3. An engineer who *wants* to own inference (data-locality or compliance requirements count double); 4. Tolerance for a weekend of downtime while something recompiles. That's a real profile — some of the best products in the space run this way. It just isn't most products, and it's almost never products at the *start*, when iteration speed is worth more than margin. ## The hybrid that actually works The pattern we see succeed: **prototype and launch on the API** (a [key](/signup), 50 free credits, five-minute [quickstart](/docs/quickstart)), instrument your real traffic, and revisit the math at your genuine volume — flat public prices make the [API side of the spreadsheet](/pricing) a five-minute job. If chat volume alone crosses the line, move *that* workload to your own GPU and keep images, video and voice on the API. Statelessness makes the swap boring: it's one base URL per workload. Infrastructure decisions deserve receipts, not vibes — run both columns on your own numbers. --- # Credits vs tokens — pricing AI for products, not benchmarks > Why we price generative AI in flat credits instead of per-token metering, what it costs us, and how to model your unit economics on top of it. Published 2026-08-18 · canonical: https://eroq.ai/blog/credit-pricing-for-ai-apis · markdown: https://eroq.ai/blog/credit-pricing-for-ai-apis.md Every AI API prices in tokens because tokens are what the provider pays for. That logic is airtight and it produces a terrible developer experience. This post is about why we broke with it. ## The problem with per-token pricing Per-token pricing pushes the provider's uncertainty onto you. Your cost per conversation now depends on how chatty your users are, how long your prompts grow, and how verbose the model felt today. You discover your unit economics *after* launch, in a dashboard, usually as a surprise. For an assistant product answering support tickets, fine — volumes are predictable. For a **character product**, it is hostile: roleplay conversations are long, hot-temperature, and user-driven. The better your product works, the less predictable your bill. ## What flat pricing costs us Let's be honest about the trade: flat pricing means we absorb variance. A completion that runs to our token cap costs us more than one that stops at two sentences, and we charge the same 1 or 3 credits. It works for three reasons: 1. **Caps make variance bounded.** `max_tokens` tops out at 1,200 and context at 24k characters. Within those walls, the spread between a cheap call and an expensive one is small enough to average. 2. **Averages are stable at volume.** One conversation is unpredictable; ten thousand are a distribution. We price on the distribution and re-measure it monthly. 3. **Simplicity is worth margin.** A price you can explain in four words — "an image is ten credits" — converts better than a price that needs a calculator. We would rather win the account than win the basis points. ## Why prepaid credits and not a monthly bill Prepaid flips the risk. With postpaid metering, your bug is your bill: a retry loop at 3 a.m. is real money by breakfast. With prepaid credits, the worst case of any runaway is your current balance — the system fails *closed*, with an explicit `402`, instead of failing expensive. It also makes the failure path honest on our side: **a generation that returns nothing refunds itself**, as a visible negative line in your ledger. Under postpaid metering, failed calls quietly bill; under prepaid credits, an unfair charge would sit in your face on the billing page. The incentive points the right way. Two design choices keep prepaid from feeling like a gift card racket: credits **never expire**, and bigger packs add **bonus credits** instead of changing per-call prices — so the price of an image is one number everywhere, forever. ## Modeling your economics on top The napkin math a character product needs: ``` revenue_per_user = your_subscription_price model_cost = msgs_per_day × credit_cost × $0.01 × 30 premium_moments = (images × 10 + voice_lines × input/100 × 3) × $0.01 margin = revenue - model_cost - premium_moments ``` Run it for your P50 and P95 user, not your average — the P95 roleplayer sends 10× the messages. Flat per-call pricing makes the P95 case *computable*, which is exactly what token pricing takes away from you. ## When token pricing is right If you are batch-processing documents with tight output control, token metering is genuinely fairer — you would be subsidizing chatty products under our model. Flat credits shine when usage is interactive, long-tailed and user-driven. That is character products, which is who we build for. The [pricing table](/pricing) has every number. They fit on one screen — that, too, is the point. --- # Let your users send pictures — vision in roleplay chat > Image input is the highest-retention feature per credit in character products. The UX patterns that work, the implementation in one request, and the moderation duties that stay on your side. Published 2026-08-14 · canonical: https://eroq.ai/blog/let-users-send-pictures-vision-roleplay · markdown: https://eroq.ai/blog/let-users-send-pictures-vision-roleplay.md Text roleplay has a one-way intimacy problem: the character describes her world, the user can only describe his back. Vision input closes the loop — he sends a photo of his desk, his dog, his dinner, and she *reacts to his actual day*. In production character apps, that reaction moment is one of the strongest retention events per credit spent. ## The feature, mechanically [Chat completions](/docs/chat) accept image parts on user turns: `content` becomes an array mixing text and `image_url` entries (https URL or data URI, up to 2 images per request). Each attached image adds **2 credits** on top of the completion — a photo-reaction turn on RP mini costs 3 credits total, about three cents. ```json { "model": "eroq-rp-mini", "context": "Mira: sardonic starship mechanic. Relationship: three weeks in.", "messages": [{ "role": "user", "content": [ { "type": "text", "text": "look what i built today" }, { "type": "image_url", "image_url": { "url": "data:image/jpeg;base64,…" } } ] }] } ``` The engine looks at the image and the character responds **in character** — not with an image caption. That distinction is the product: "That's a beautiful golden retriever" is a vision demo; "*wipes grease off her hands* okay, the dog is cuter than you. What's his name?" is a relationship. ## UX patterns that earn their credits - **The reaction moment.** A camera button in the chat composer, full stop. Users discover it themselves; the first "she noticed the details" reply is the hook. - **Prompted shares.** Have the character *ask* — "show me where you're sitting right now" — at natural beats. Prompted photos convert 3–4× better than a passive button, and they pace your vision spend. - **Memory callbacks.** Fold what she saw into your rolling summary ("his desk faces a window; the dog is Biscuit"). A callback two days later — "how's Biscuit?" — is the cheapest wow in the category, and it costs zero extra credits because it's just [context](/docs/chat). - **Cap it visibly.** Two images per request is the API ceiling; a per-day allowance in your free tier keeps the unit economics boring. Vision is cheap per event, not free at scale — budget it like every other [flat price](/pricing). ## The responsibilities that stay yours User-submitted images are user-generated content, and the duties that come with UGC don't transfer to your model provider: - **Scan uploads on your side** (CSAM detection against industry hash lists at minimum) *before* the API call. eroq's [acceptable-use policy](/legal/aup) bans minors and non-consensual real-person content absolutely — requests outside it return `content_blocked` and are never charged — but detection tooling on the upload path is your legal surface, not a nice-to-have. - **Age-gate the feature** with the rest of your product. Photo exchange belongs behind the same adults-only wall as the roleplay itself. - **Store nothing you don't need.** Pass data URIs through and keep only what the product requires (the summary line, not the photo). If the user *unlocks her sending photos back*, that's [image generation](/docs/images) — and those you can host durably with [`store: true`](/storage). ## Ship it in an afternoon Vision input is the rare feature that's one composer button, one array change in an existing call, and no new infrastructure. A [key](/signup) and the 50 free credits cover the whole test: send the API a photo of your own desk and watch the character notice the coffee cups. That reaction is the feature — the rest is product discipline. --- # Streaming AI responses over SSE — a practical guide > Server-sent events end to end — parsing chunk frames correctly, relaying streams through your own backend, and the buffering bugs that only appear in production. Published 2026-08-12 · canonical: https://eroq.ai/blog/streaming-sse-practical-guide · markdown: https://eroq.ai/blog/streaming-sse-practical-guide.md Streaming is the difference between a character who is *present* and a spinner. The mechanics are simple; the bugs are all in the details this guide covers. ## The wire format With `stream: true`, `/v1/chat/completions` answers `text/event-stream`. Each frame is a `data:` line and a blank line; deltas arrive in the standard chunk shape: ``` data: {"id":"cmpl_…","object":"chat.completion.chunk","choices":[{"delta":{"content":"That "}}]} data: {"id":"cmpl_…","object":"chat.completion.chunk","choices":[{"delta":{"content":"noise"}}]} data: {"object":"chat.completion.usage","usage":{"credits_spent":3,"credits_remaining":997}} data: [DONE] ``` Two eroq-specific frames worth knowing: the **usage event** just before `[DONE]` carries the meter, and an **error event** (`{"error":{…}}`) replaces the crash you would otherwise have to infer from a dropped connection. If the stream errors before any content arrived, the call has already refunded itself. ## Parsing without the classic bug The classic bug: treating every network chunk as a complete frame. TCP does not respect your line breaks — a frame can arrive split across reads. Buffer, split on `\n\n`, and keep the remainder: ```js const reader = res.body.getReader() const decoder = new TextDecoder() let buffer = '' while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) // stream: true matters for UTF-8 const frames = buffer.split('\n\n') buffer = frames.pop() ?? '' // last piece may be incomplete for (const frame of frames) { const data = frame.replace(/^data: /, '') if (data === '[DONE]') continue const parsed = JSON.parse(data) if (parsed.error) throw new Error(parsed.error.message) const delta = parsed.choices?.[0]?.delta?.content if (delta) render(delta) } } ``` The `{ stream: true }` on `decoder.decode` is not decoration: without it, a multi-byte character split across chunks becomes mojibake. Emoji-heavy roleplay finds this bug within the hour. ## Relaying through your backend Never ship your API key to a browser — relay the stream. The relay is thin, but two details make or break it: ```js // Node/Express-style relay app.post('/chat', async (req, res) => { res.setHeader('Content-Type', 'text/event-stream') res.setHeader('Cache-Control', 'no-cache') res.flushHeaders() // 1. headers out immediately const upstream = await fetch('https://eroq.ai/v1/chat/completions', { /* … */ }) for await (const chunk of upstream.body) { res.write(chunk) // 2. relay bytes, re-frame nothing } res.end() }) ``` 1. **Flush headers immediately**, or your reverse proxy may buffer the whole response and deliver it at once — streaming that arrives as a block. On nginx, also set `X-Accel-Buffering: no`. 2. **Relay bytes verbatim.** Parsing and re-serializing frames in the relay doubles your bug surface for zero value. Parse on the client, where you render. ## UX details that separate good from great - **Render on a small timer** (30–50ms), not per-delta — per-token DOM writes jank on mobile. - **Show the first token fast, then let it flow.** Perceived latency lives almost entirely in time-to-first-token. - **On mid-stream failure, keep the partial text** and offer a retry affordance. A half-reply that stays beats a reply that vanishes — and since interrupted streams past first output were still generated, keeping the text respects what was paid for. - **Let users abort.** Closing your relay's response should close the upstream request; an abandoned stream you keep consuming is money spent rendering to nobody. Flat pricing has one more consequence here: streaming costs exactly what buffering costs — the [same 1 or 3 credits](/pricing). There is no reason not to stream, which is why every example in [the docs](/docs/streaming) does. --- # Giving AI characters a voice — TTS latency, cost and craft > Where voice fits in a character product, how to keep synthesis costs sane, and the delivery details that make a generated line feel performed instead of read. Published 2026-08-05 · canonical: https://eroq.ai/blog/tts-for-ai-characters · markdown: https://eroq.ai/blog/tts-for-ai-characters.md Text gives a character a mind; voice gives them a body. The retention delta is real — a character who *speaks* gets opened again. But voice is also the easiest feature to build badly: expensive, slow, and flat. This is the playbook we use. ## Voice as a moment, not a modality The instinct is to voice everything. Resist it. Synthesis costs real money per character of input ([3 credits per 100 characters](/pricing) here), and a wall of autoplayed audio is fatiguing anyway. Voice works as **punctuation**: the greeting when a conversation opens, an emotional beat, a reply the user explicitly asks to hear. Products that treat voice as a premium *moment* spend a tenth as much and get more retention from it, because scarcity is what makes it land. ## The cost model `POST /v1/audio/speech` meters per started 100-character block. Practical numbers: | Line | Characters | Credits | ~Cost | | --- | --- | --- | --- | | Short greeting | 80 | 3 | $0.03 | | Typical reply | 240 | 9 | $0.09 | | Long dramatic beat | 600 | 18 | $0.18 | Two implications. First, **write for voice**: a spoken line should be shorter than a written one anyway — trimming to 200 characters is a craft improvement that halves your bill. Second, **cache aggressively**: greetings, catchphrases and recurring beats are the same audio every time. Hash `voice + text`, store the MP3 yourself (outputs are not retained on our side and are yours to keep), and a huge share of playbacks become free. ## Latency and the perception of it Synthesis takes a second or two. The trick is that *perceived* latency is a UI property: - Show the text reply immediately; let the audio arrive behind it with a subtle "voice loading" affordance. Users read while it renders. - For scripted moments (onboarding, greetings), pre-generate — those lines are known before the user ever arrives. - Never block the conversation on audio. Voice that fails should degrade to text silently, not error the turn. ## Making it feel performed The difference between "read aloud" and "performed" is mostly in the input text: - **Punctuation is prosody.** Commas, dashes and ellipses are how you direct pacing. "Well… that's new." performs; "Well that is new" reads. - **Keep character voice in the words.** The synthesis voice carries timbre; the *writing* carries personality. A sardonic character needs sardonic sentences, not a sardonic voice setting. - **Match voice to character once, then never change it.** Voice identity is character identity — switching voices mid-relationship is the audio equivalent of swapping the avatar. The roster (`aria`, `orion` — see [the reference](/docs/speech)) is curated rather than infinite on purpose: two voices that always sound right beat forty that sometimes do. ## The round trip: ears too The same audio pipeline runs the other way — [`/v1/audio/transcriptions`](/docs/transcriptions) (5 credits) turns a user's voice message into text your character can answer. Voice in, text reasoning, voice out is the full loop of a "call mode", and each leg is one API call. Start with greetings: one cached line per character, a few credits each, shipped in an afternoon. Measure what it does to day-two retention, then decide how much further to take it. In our experience, that first afternoon is the one that changes the product. --- # Image generation for character products — prompts, consistency, cost > Prompting a diffusion engine well, keeping a character recognizable across generations, and budgeting image features at 10 credits a render. Published 2026-07-28 · canonical: https://eroq.ai/blog/image-generation-for-characters · markdown: https://eroq.ai/blog/image-generation-for-characters.md Chat retains; images convert. A character the user can *see* — in new scenes, new outfits, new moods — is the single strongest premium mechanic we know in this category. Here is how to build the feature well against `eroq-image-one`. ## Speaking diffusion Image One is a diffusion engine, and diffusion has a dialect. It reads both prose and comma-separated tags; what it rewards is **specificity in the right order**: ``` subject and identity, action or pose, outfit, setting, lighting, camera, style ``` ```json { "model": "eroq-image-one", "prompt": "young woman with silver bob and amber eyes, leaning on a workbench, grease-stained overalls, spaceship hangar, warm practical lights, 35mm, shallow depth of field", "negative_prompt": "blurry, extra fingers, watermark, text" } ``` Three rules that do most of the work: - **Front-load identity.** Tokens earlier in the prompt weigh more. The character's defining traits — hair, eyes, build — come first, every time. - **Always send a negative prompt.** `blurry, extra fingers, watermark, text` is the floor; add whatever your style must exclude. It is a free quality lever. - **One scene per prompt.** Diffusion averages competing instructions into mush. "In the hangar AND at the beach" produces neither. `cfg_scale` (prompt adherence) defaults sensibly; raise it toward 9–12 when the engine takes too much creative liberty, drop toward 4–6 when results look overcooked. ## The consistency problem The hard problem in character imagery is that a diffusion model invents a new face every call. You cannot fully prompt your way out — but you can get surprisingly far: 1. **Fix a canonical description.** Write one 15–25 token identity block per character and prepend it *verbatim* to every image prompt. Word-for-word stability matters; synonyms drift the face. 2. **Fix the style block too.** A consistent rendering style ("35mm, soft grain") makes faces read as the same person even when features wobble. 3. **Seed galleries, don't stream them.** For a character's public gallery, generate in batches, curate the on-model results, discard the rest. At [10 credits a render](/pricing), a curated 12-image gallery costs about $1.50 including rejects — price the feature, not the attempt. Refused or empty generations refund automatically, and a prompt the engine declines returns an explicit `content_blocked` code rather than a silent failure — so your retry logic can tell "rephrase" from "try again". ## Where images fit the product The pattern that converts, in order of effort: - **The reveal** — a one-time "see them" moment early in a relationship. One image, massive activation effect. - **Scene stills** — user-triggered "show me this moment" during chat. Charge your users per render; your cost is a known 10 credits. - **The gallery** — curated, drip-released, subscription-gated. Batch-generated off-peak. Each maps to one `POST /v1/images/generations` call — the [reference](/docs/images) covers the parameters, and outputs return inline (base64) for you to store; nothing is retained on our side. ## Budget table | Feature | Renders/user/mo | Credits | ~Cost | | --- | --- | --- | --- | | Reveal (once) | 1 | 10 | $0.10 | | Scene stills | 12 | 120 | $1.20 | | Gallery drops (curated 3:1) | 16 | 160 | $1.60 | Against a $10–15/month subscription, imagery lands comfortably inside margin while being the most visible thing the subscription buys. That combination — cheap to run, premium to perceive — is why it is the first feature we tell builders to add after chat.