---
title: "Uncensored Discord bot: build your own in Python"
canonical: https://uncensoredllm.ai/guides/discord-bot
generated: 2026-10-27T08:55:18.698Z
source: https://uncensoredllm.ai/guides/discord-bot
base_url: https://api.uncensoredllm.ai/v1
---

# Build an uncensored Discord bot in one Python file

An uncensored Discord bot you run yourself: one discord.py file, the OpenAI SDK on this API, three env vars, and replies only in age-restricted channels.

Steps checked October 25, 2026 · prices as of October 27, 2026

This uncensored Discord bot is one Python file you run yourself. It answers @mentions with a model from this API through the OpenAI SDK, reads three environment variables, and sends prompts to the model only from age-restricted channels, which is where Discord's rules put adult content. The code was checked against the discord.py and OpenAI SDK docs on 25 October 2026.

If builds with the refusals edited out are new to you, start with [AI with no restrictions](https://uncensoredllm.ai/blog/ai-with-no-restrictions), then come back here to put one in a channel.

## Why discord.py for this bot

discord.py and the OpenAI Python SDK both run on asyncio. The SDK's `AsyncOpenAI` client waits for the model on the same event loop as the bot, so one slow reply does not hold up the next message. The whole bot is one file and two pip packages, with no build step.

discord.js with the OpenAI Node SDK can do the same job. Python wins here on length: the file below is short enough to read in one pass. Versions checked on 25 October 2026: discord.py 2.7.1 and openai 3.37.0 on PyPI. The openai package needs Python 3.10 or newer.

## The three environment variables

- DISCORD_TOKEN: your-bot-token From the Bot page of your Discord app
- ULLM_API_KEY: sk-ullm-live-… Created in the console, shown once
- ULLM_MODEL: glm-5.3-flash-uncensored Optional; any id from the models page

The base URL, `https://api.uncensoredllm.ai/v1`, is written into the file. Create the key in the [console](https://app.uncensoredllm.ai/console); it starts with `sk-ullm-` and spends a prepaid balance that starts at $10.

## Create the Discord app and its token

The labels below are from Discord's [getting started guide](https://docs.discord.com/developers/quick-start/getting-started), read on 25 October 2026.

1. In the Developer Portal, create an app: enter a name and press **Create**.
2. On the **Bot** page, under **Token**, press **Reset Token** and copy it into `DISCORD_TOKEN`. Discord shows it once.
3. On the **Installation** page, choose the Discord Provided Link. Under **Default Install Settings**, add the `bot` scope for Guild Install.
4. In the permissions menu that appears, pick Send Messages and Read Message History. Discord's message docs require Read Message History for a reply to another message, and the bot replies to the message that mentioned it.
5. Open the install link, choose **Add to server**, and make sure the bot can see the age-restricted channel.

No privileged intent is needed. The bot reads only messages that mention it, and Discord's [gateway docs](https://docs.discord.com/developers/events/gateway) list "content in which the app is mentioned" as an exception to the Message Content intent.

## The uncensored Discord bot file

Save this as `bot.py`. Not as `discord.py`: the discord.py quickstart warns that the name conflicts with the library.

```python
# bot.py: answers @mentions in age-restricted channels with an uncensored model.
import os
import re

import discord
from openai import APIStatusError, AsyncOpenAI

MODEL = os.environ.get("ULLM_MODEL", "glm-5.3-flash-uncensored")
SYSTEM = (
    "You are a blunt, helpful member of this Discord server. "
    "Answer in plain text, under 1,500 characters."
)

llm = AsyncOpenAI(base_url="https://api.uncensoredllm.ai/v1", api_key=os.environ["ULLM_API_KEY"])
client = discord.Client(intents=discord.Intents.default())

def age_restricted(channel) -> bool:
    # Guild text channels and threads have is_nsfw(); DMs do not, so the bot stays quiet there.
    is_nsfw = getattr(channel, "is_nsfw", None)
    return bool(is_nsfw and is_nsfw())

@client.event
async def on_ready():
    print(f"Logged in as {client.user}")

@client.event
async def on_message(message: discord.Message):
    if message.author.bot or client.user not in message.mentions:
        return
    if not age_restricted(message.channel):
        await message.reply("I only answer in age-restricted channels.")
        return
    prompt = re.sub(rf"<@!?{client.user.id}>", "", message.content).strip()
    if not prompt:
        return
    try:
        async with message.channel.typing():
            reply = await llm.chat.completions.create(
                model=MODEL,
                messages=[
                    {"role": "system", "content": SYSTEM},
                    {"role": "user", "content": prompt},
                ],
                max_tokens=500,
            )
    except APIStatusError as err:
        await message.reply(f"The model API answered {err.status_code}.")
        return
    text = reply.choices[0].message.content or "(empty reply)"
    await message.reply(text[:2000])

client.run(os.environ["DISCORD_TOKEN"])
```

It answers an @mention with one request to the API and replies in the same channel. A reply is cut at 2,000 characters, the limit Discord's message docs give for message content. Each mention is a fresh request with no history, which keeps every call small.

## Discord's age-restricted channel rule

Discord's [Community Guidelines](https://discord.com/guidelines) (effective 29 September 2025, read on 25 October 2026) cover this in rule 9: "Do not make sexually explicit content available to anyone under the age of 18." The same rule says users must be 18 or older to view adult sexual content, and server owners must apply an age-restricted label to channels that contain it.

An uncensored model can write what a filtered one will not, so the bot checks the channel before it answers. Discord's API calls the flag `nsfw` and describes it as "whether the channel is age-restricted". discord.py exposes it as `is_nsfw()` on channels and threads. Direct messages carry no such flag, so a mention there, or in any unlabeled channel, gets a one-line notice and never reaches the model.

Discord also publishes a [Developer Policy](https://support-dev.discord.com/hc/en-us/articles/8563934450327-Discord-Developer-Policy) for apps; read it before you add the bot to servers you do not run. Use of this API also falls under our [acceptable use policy](https://uncensoredllm.ai/acceptable-use).

## Run the bot

```bash
python3 -m pip install discord.py openai
export DISCORD_TOKEN="your-bot-token"
export ULLM_API_KEY="sk-ullm-live-…"
export ULLM_MODEL="glm-5.3-flash-uncensored"   # optional
python3 bot.py
```

"Logged in as" in the terminal means Discord accepted the token. In the age-restricted channel, mention the bot with a request, for example a villain's monologue for your campaign. A typing indicator shows while the model answers. The bot runs as long as the script does, on your machine or any small server.

## Which uncensored build to use

Three builds, with the figures the catalog gave when this page rendered and today's verdict from [the daily real-model check](https://uncensoredllm.ai/proof), which sends a fixed prompt to every model each day:

| Model | Build | Input $/M | Output $/M | Context | Wire formats |
| --- | --- | --- | --- | --- | --- |
| [GLM-5.3 Flash Uncensored](https://uncensoredllm.ai/models/glm-5.3-flash-uncensored) glm-5.3-flash-uncensored | Uncensored | $0.1086 | $0.3802 |  | openai |
| [GLM-5.3 Uncensored](https://uncensoredllm.ai/models/glm-5.3-uncensored) glm-5.3-uncensored | Uncensored | $0.282 | $0.846 |  | openai |

Prices per million tokens, read from the live catalog on 2026-10-27.

| Model id | Daily check | Context window |
| --- | --- | --- |
| [`glm-5.3-flash-uncensored`](https://uncensoredllm.ai/models/glm-5.3-flash-uncensored) | Steady since Oct 12 | not published |
| [`glm-5.3-uncensored`](https://uncensoredllm.ai/models/glm-5.3-uncensored) | Steady since Oct 12 | not published |

Windows from the live catalog on 2026-10-27; verdicts from /proof at render.

The flash build is the default for a busy channel. For a character bot, set `ULLM_MODEL` to `roleplay-uncensored` and write the character into `SYSTEM`. More character builds are listed with the [Character AI alternatives](https://uncensoredllm.ai/blog/character-ai-alternatives).

## What a busy server costs

The worked example: 300 replies a day for 30 days. Each request sends about 200 tokens (the system prompt and the message) and gets about 300 back.

| Model | 9,000 replies a month |
| --- | --- |
| [GLM-5.3 Flash Uncensored](https://uncensoredllm.ai/models/glm-5.3-flash-uncensored) | $1.22 |
| [GLM-5.3 Uncensored](https://uncensoredllm.ai/models/glm-5.3-uncensored) | $2.79 |

Computed at render from the catalog prices on 2026-10-27: replies × 200 × input price, plus replies × 300 × output price.

Adding memory changes this most. Send the last ten messages as history and every request grows by their length. The `max_tokens=500` line caps the reply side. Every per-token rate is on the [pricing page](https://uncensoredllm.ai/pricing).

## Check the key before the bot

This lists the model ids the key can call. It is free and takes a second.

```bash
curl https://api.uncensoredllm.ai/v1/models \
  -H "Authorization: Bearer sk-ullm-live-…"
```

A 200 with a list of ids means the key works and `ULLM_MODEL` only has to match one of them. A 401 says which way the key is wrong:

```
HTTP 401
{"error":{"message":"No API key was sent.","type":"authentication_error","param":null,"code":"invalid_api_key"}}
```

```
HTTP 401
{"error":{"message":"The API key is not valid.","type":"authentication_error","param":null,"code":"invalid_api_key"}}
```

Both bodies as the API returned them on 25 October 2026.

## Troubleshooting the Discord bot

### The bot replies "The model API answered 401"

`ULLM_API_KEY` is empty, wrong or revoked. Run the curl above with the same key.

### "The model API answered 404"

`ULLM_MODEL` is not an id in the catalog:

```
HTTP 404
{"error":{"message":"No such model: glm-5.3-flash-uncensord.","type":"not_found_error","param":null,"code":"model_not_found"}}
```

### "The model API answered 402"

The prepaid balance is at zero. Top up in the console; a refused request is not charged.

```
HTTP 402
{"error":{"message":"The prepaid balance does not cover this request. Top up at … to continue.","type":"insufficient_quota","param":null,"code":"insufficient_balance"}}
```

### PrivilegedIntentsRequired on start

The code asks for an intent the Developer Portal has not switched on; Discord closes the connection with code 4014. The file above asks for none. If you add `message_content`, enable it on the Bot page as well.

### Forbidden when the bot replies

discord.py raises `Forbidden` when the bot lacks a permission. Check Send Messages and Read Message History on that channel. Base URL, key and model id for other clients are on the [docs page](https://uncensoredllm.ai/docs).

## Questions

### Is there a ready-made uncensored Discord bot?

Bot directories such as top.gg and discord.bots.gg list hosted ones. Running your own, as here, means you pick the model and pay per reply on your own key.

### Do uncensored Discord bots have to stay in age-restricted channels?

Discord’s Community Guidelines (rule 9) require an age-restricted label on channels with sexually explicit adult content. This bot sends prompts to the model only from channels that carry the label; anywhere else it replies with a one-line notice.

### Does the bot need the Message Content intent?

No. It reads only messages that mention it, and Discord delivers those without the intent. Asking for the intent in code without enabling it in the Developer Portal makes discord.py raise PrivilegedIntentsRequired.

### Can I build it with discord.js instead?

Yes. Use the OpenAI Node SDK with baseURL https://api.uncensoredllm.ai/v1 and your key; the logic is the same. The docs page has the Node snippet. See the docs page.

### Which model should an uncensored Discord bot use?

Any id that takes the OpenAI wire format works. A flash build keeps a busy channel cheap; a roleplay build suits a character bot. See the uncensored model list.

### How much does an uncensored Discord bot cost to run?

Only the tokens: the table above prices a month of replies at the live catalog rates. There is no subscription; the balance is prepaid from $10. See per-token prices.

[Get a key](https://app.uncensoredllm.ai/console) Prepaid from $10, no subscription.

## New models and price changes, once a month.

New uncensored builds with their prices, price moves, and what the daily model check found.
