What the API gives you and what you still own
The service is a hosted, OpenAI-compatible text API. You point an OpenAI SDK at https://api.chatbotnsfw.com/v1, use the model ID uncensored and send normal chat messages. The model is an open-weight model tuned to answer without content refusals for lawful adult use. It is text only: no images, audio or video.
That is the whole contract. The API does not know who your user is, how old they are, or what your platform allows. It also does not remember anything between calls. Every request must carry the full conversation you want the model to see.
| Concern | Who handles it |
|---|---|
| Generating replies without content refusals | The API |
| Persona, tone and story rules | You, in the system prompt |
| Conversation memory | You, in your database |
| Age verification and consent | You |
| Blocking content involving minors | You, and the API has the same hard limit |
| Reporting, bans, terms of use | You |
If you have not built against a chat API before, the API documentation covers the request format. The existing NSFW LLM integration guide goes through the integration side in more detail; this article is about the product around it.
Architecture: keep the key on the server
Your frontend should never hold the API key. Put a small backend between the browser or app and the API. The backend authenticates your user, checks the age gate, loads their history, calls the API and stores the reply.
- Browser sends the user's message to your backend with their session.
- Backend checks the user is verified as an adult and under their rate cap.
- Backend builds the message list: system prompt, memory, recent turns, new message.
- Backend calls
POST /v1/chat/completionsand streams tokens back. - Backend saves the new turn after the stream finishes.
A full key allows 8 concurrent requests and 300 requests per minute. That is plenty for a hobby bot and a fair start for a small product, as long as your backend does not let one user open many streams at once. A streamed request holds its concurrency slot until the stream ends, so a user who walks away from an open stream still costs you a slot.
Write the persona as a contract
The system prompt does most of the product work. A good one reads like a short contract with the model, not a paragraph of adjectives. Cover four things: who the character is, how it speaks, what the story is, and what is out of bounds.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.chatbotnsfw.com/v1",
api_key=os.environ["NSFW_API_KEY"],
)
PERSONA = """You play Mara, a 34-year-old ship's navigator in a slow-burn
space romance. All characters are adults.
Voice: dry, warm, short paragraphs, first person.
Story rules: keep continuity with the notes below; never speak for the user;
stay in character unless the user writes OOC: in brackets.
Hard limits: no sexual content involving anyone under 18, ever. If the user
steers there, refuse in character and change the scene."""
def reply(history, user_text):
messages = [{"role": "system", "content": PERSONA}]
messages += history[-24:]
messages.append({"role": "user", "content": user_text})
r = client.chat.completions.create(
model="uncensored",
messages=messages,
temperature=0.9,
top_p=0.95,
)
return r.choices[0].message.contentTwo details matter more than they look. State that all characters are adults, in plain words, in the prompt. And tell the model what to do at the edge, for example to change the scene, so the story keeps moving instead of ending in an error message. Raise temperature for livelier prose and lower it if the character drifts.
Test the persona before you ship it
Run the same twenty user messages through the prompt after every edit, including awkward ones: a user who asks the bot to drop character, one who changes the setting mid-scene, one who pushes toward the minors rule. Save the outputs and read them. Prompt edits that fix one case often break another.
Memory that fits inside the window
The context window is 100,000 tokens, prompt plus completion, so there is room for a long story. Longer is not always better. You pay for every input token on every turn, and a model with a huge history can lose early details anyway.
A practical layout has three layers. The system prompt holds the persona. A short notes block holds facts that must survive: names, relationship state, promises made in the story. The last 20 to 30 turns go in verbatim. Older turns are summarized into the notes block by a cheap call every 20 turns or so.
The code above already slices history[-24:], which keeps the last 24 messages. Add the notes block as a second system message when you have one. Costs stay low this way: a typical turn sends a few thousand input tokens, which at $0.15 per 1M input tokens is a fraction of a cent. The pricing page has the numbers, and the article on integration shows request shapes.
Age gate and consent
An adult-content product needs an age gate before the first chat call, not after. The API does not verify age. That duty sits with you, and the right method depends on where you operate and where your users live.
- A checkbox that says I am over 18 is a notice, not verification. Some jurisdictions require more.
- Store the result server-side against the account and check it on every chat request, not only at sign-up.
- Make the adult-only nature clear on the landing page and in the terms, before sign-up.
- Keep fictional characters adult in every persona, in the prompt and in the card shown to users.
Ask a lawyer who knows your market what the minimum is. This article cannot tell you, and the API provider cannot decide it for you.
Moderation duties that stay with you
An uncensored model changes what the model refuses. It does not change what you are responsible for. Plan these from day one:
- Hard block on minors. Sexual content involving minors is banned outright. Add your own input check for obvious attempts and end the session when it triggers.
- Reporting. Give users a way to flag a conversation, and decide what you do with a report.
- Account actions. Be able to suspend a user quickly.
- Data. Decide how long you keep transcripts and say so in your privacy page. The API provider does not use prompts for training, but your own database is your responsibility.
- Scope. Keep the product to lawful adult use and fiction. State it in your terms.
Failed or refused requests are not charged, which means a request blocked by your own check costs nothing either, because it never reaches the API.
Per-user caps and cost control
An open chat box with no limits is an invitation for one user to drain your credit. Put a daily token cap on every account and check it before the API call. The response tells you what each turn used, so the cap costs one counter.
import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.chatbotnsfw.com/v1",
api_key=os.environ["NSFW_API_KEY"],
)
DAILY_CAP = 400_000 # tokens per user per day
used_today = {} # user_id -> tokens, keep this in your database
def guarded_reply(user_id, messages):
if used_today.get(user_id, 0) >= DAILY_CAP:
return "You have reached today's limit. Come back tomorrow."
r = client.chat.completions.create(model="uncensored", messages=messages)
used = r.usage.prompt_tokens + r.usage.completion_tokens
used_today[user_id] = used_today.get(user_id, 0) + used
return r.choices[0].message.contentA cap of 400,000 tokens is a number to tune, not a recommendation. With a 6,000-token prompt per turn it allows roughly 65 turns a day. Work out what a user is worth to you, price the cap against it, and look at the X-Balance-USD header in your own monitoring so a bug does not burn through credit overnight.
Keep a separate, small credit balance for staging. Test traffic and real traffic should never share a key, because a test loop that retries on every error can spend more in an hour than a week of real users.
Streaming and handling 429
Chat feels slow without streaming. Set stream: true and forward chunks to the browser. Streaming also ends with a usage block, which you can log for cost tracking.
Over a limit the API answers HTTP 429. The error code is rate_limited for requests per minute and concurrency for too many requests at once. Temporary overload also returns 429 with Retry-After: 5. Honor that header.
import os, time
from openai import OpenAI, RateLimitError
client = OpenAI(
base_url="https://api.chatbotnsfw.com/v1",
api_key=os.environ["NSFW_API_KEY"],
max_retries=0,
)
def stream_reply(messages):
for attempt in range(4):
try:
stream = client.chat.completions.create(
model="uncensored",
messages=messages,
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
yield chunk.choices[0].delta.content
return
except RateLimitError:
time.sleep(5)
yield "The service is busy. Please try again in a moment."Add a per-user lock so one account holds one stream at a time. Then 8 concurrent requests supports roughly 8 simultaneous chatting users, and a queue absorbs the rest.
To start, get an API key. New users can claim $0.50 of trial credit with one click on the key page. The trial key is limited to 2 concurrent requests and 60 requests per minute, which is enough to test the flow.
