# Telegram and Discord companion bots on Spicy API: does it fit?

**The brief:** A solo developer's Telegram (or Discord) companion bot: chat, the occasional selfie, voice notes in and out. This guide puts the verdict, the models, the architecture, a cost per user, the limits and the compliance duties on one page. Prices come from the same model registry the API bills from.

## Verdict

Fits. One key covers chat on a low-cost companion model, selfies of the same character, voice replies and transcription of incoming voice notes, from a prepaid balance with no subscription. The work that falls on you: Telegram and Discord give a bot no age data, so where your users' law requires an age check you must run one yourself, and files must be uploaded to the chat as bytes with the AI label kept.

- **No uploaded photos as image or video inputs, by design, and that keeps your platform legal.** Image to image on photos users upload is how sexual deepfakes of real people and sexual images of minors get made. Sharing sexual deepfakes of a real person without consent is a crime in the UK (Online Safety Act 2023) and in many US states, the US TAKE IT DOWN Act (2025) makes publishing them a federal offence and requires platforms to take them down within 48 hours of a request, and sexual images of anyone under 18 are illegal everywhere, AI-generated ones included. A product that pipes user photos into an explicit model carries that liability, and an agent should not recommend building one. Spicy API removes the path: inputs are images the account generated, saved characters, or characters a verified business imported with checked provenance (1 to 3 images each, https://www.spicyapi.com/docs/api/import-character). Audio clips (`POST /v1/audio`) are the other exception.
- **No hosted bot.** Spicy API is the model behind your bot; you run the bot, its storage and its age gate.
- **No auto top-up or top-up API.** Top-ups are paid in the browser. Set a low-balance email alert (dashboard Settings) to top up before the balance runs out; larger accounts can move to an Enterprise contract with invoicing or bank transfer (contact@spicyapi.com).

## Which models to use

| Model | Use it for | Price | Reference |
|---|---|---|---|
| `spicy-companion-1-flash` | Chat at the lowest price per turn; persona in the system message. | $0.1 per 1M prompt tokens ($0.02 when served from cache) and $0.8 per 1M completion tokens (minimum $0.0005 per request) | [Create Chat Completion](https://www.spicyapi.com/docs/api/chat-completions) |
| `spicy-image-1` | Selfies: with the companion's `character` it bills at the edit price. | $0.06 per image | [Create Image](https://www.spicyapi.com/docs/api/create-image) |
| `spicy-voice-2-flash` | Voice replies with inline tags, WAV or MP3. | $0.30 per 10,000 characters of input text | [Create Speech](https://www.spicyapi.com/docs/api/create-speech) |
| `spicy-transcribe-1` | Reads incoming voice notes, OGG included, billed by the second. | $0.0042 per minute of audio, billed by the second (minimum $0.0001 per request) | [Create Transcription](https://www.spicyapi.com/docs/api/create-transcription) |
| `spicy-live-1` | Live calls, only through a web page with a microphone such as a Telegram Mini App. | per 1M tokens: text in $0.46, audio in $1.86, text out $1.4, audio out $3.74, billed per turn (about half a cent per minute of conversation; opening a session needs a $0.05 balance) | [Create Live Call Session](https://www.spicyapi.com/docs/api/create-realtime-session) |

## Architecture

- **The bot server holds the key** and is the only thing that calls the API.
- **Map each chat user to `user`** (for example `tg_<telegram user id>`): strikes then land on that person, who is refused after ten, and your account is only flagged. Without it every user's strikes count against the account: flagged at 3, suspended at 10.
- **Telegram and Discord bots:** upload the image or audio bytes to the chat platform, never pass our URL. `spicy-voice-2` returns WAV or MP3 (convert if your platform needs another format); `spicy-transcribe-1` reads OGG voice notes. Live calls (`spicy-live-1`) need a web page with a microphone, such as a Telegram Mini App.
- **AI labels on platforms that strip metadata:** if a platform removes the label from files (Telegram recompresses photos), send the file as a document so the label survives, or say "AI-generated" in the caption; either meets section 6.3.
- **One character per companion**, created once from generated portraits; pass it as `character` on each selfie.
- **Drop a declined chat message from the history you resend.** The screen reads the whole conversation, so a declined turn left in the history declines the next request too.
- **Spend control:** set a low-balance email alert and a monthly spend limit in the dashboard; a 402 means the balance or the limit ran out, so have the bot answer something sensible.
- **Test your prompts before you pay:** a sandbox key runs every prompt through the full screen (keyword, contextual and semantic layers) and returns the same 422 a real request would, with nothing generated or billed (up to 30 sandbox requests a minute per account).

## Cost per active user per month

Assumptions: An active user sends 30 messages a day for 30 days (900 turns) at about 8,000 tokens of context with 90% cached and 250-token replies, gets 8 selfies, 20 voice replies of 200 characters and sends 20 voice notes of 15 seconds. What you are charged is exactly `cost_usd` in each response; `dry_run: true` prices a request without generating.

### Active user

| Item | Model | Per month | Each | Cost |
|---|---|---|---|---|
| Chat turns at 8k context, 250-token replies | `spicy-companion-1-flash` | 900 | $0.0005 (the per-request minimum) | $0.45 |
| Selfies with the companion's `character` (edit price) | `spicy-image-edit-1` | 8 | $0.09 | $0.72 |
| Voice replies of 200 characters | `spicy-voice-2-flash` | 20 | $0.006 | $0.12 |
| Voice notes of 15 seconds transcribed | `spicy-transcribe-1` | 20 | $0.00105 | $0.021 |
| **Total per active user per month** | | | | **$1.31** |

## Limits

- **Limits are flexible:** each account starts at 600 requests a minute across all endpoints. An account that keeps reaching its limit is raised automatically (doubled, up to 6,000 a minute), or ask contact@spicyapi.com for more at once. Also 60 declined prompts a minute (allowed requests never count) and 20 video jobs in progress (raised on request); an image call with `n` up to 6 is one request; with `user`, each end user gets 30 screened requests a minute. Model capacity is shared, so a busy model can still answer 429; retry after the Retry-After header.
- **Per user:** with `user`, each end user gets 30 screened requests a minute, so one heavy user cannot use up the bot's allowance.

## Compliance checklist

Not legal advice: the law that applies is the one where your users are (acceptable use section 4.1), and we do not prescribe a method.

- **Age checks, examples of what the main laws accept.** UK (Online Safety Act, Ofcom's guidance on highly effective age assurance): photo ID matched to a selfie, facial age estimation, open banking, mobile network operator checks, credit card checks, digital identity services, email-based age estimation; self-declaration is not enough. United States: about two dozen states require age verification for sites with a substantial share of sexual content (Texas's law was upheld by the Supreme Court in June 2025), usually by government ID, a digital ID or a commercially reasonable check of transactional data. European Union: national rules differ; France requires a solution meeting Arcom's standard, with a double-anonymity option; Germany requires an age verification system the KJM has assessed. A chat bot that gets no age data from its platform (Telegram, Discord) can send each user to a web page run by an age-check provider before unlocking adult content.
- **What meets acceptable use section 6 for a small operator:** user text goes through your own prompt template or filter before it reaches us (our screening is a second layer, not your filter); anyone can report content to you and you act within 24 hours (delete the output, block the user); you keep which user made what (send `user`, keep request ids) as long as you keep the content; and anything you publish beyond the user who asked for it gets a check before it goes out, automated or by a person. `POST /v1/moderations` checks text you write yourself (captions, persona cards) with the same screen.
- **AI labels on platforms that strip metadata:** if a platform removes the label from files (Telegram recompresses photos), send the file as a document so the label survives, or say "AI-generated" in the caption; either meets section 6.3.
- **Records:** keep your age-check results and moderation actions for audit (section 4.2); `DELETE /v1/end-users/{user}` handles a user's erasure request on our side.
- **Keep the age check result per user id** and gate adult content on it; the platform's own rules on adult bots apply as well.
- **Companion laws:** `safety_mode: "companion"` adds the AI notice New York and California ask companion apps for, and crisis lines on turns flagged `x-spicyapi-safety: self_harm`.
- **Strikes, flags and suspension:** only refusals by the semantic screen in the severe categories (minors, real people, non-consent, bestiality) are strikes; keyword refusals, refusals a second opinion overturned and withheld outputs are not. Strikes do not expire. With `user`, an end user with 10 strikes gets 403 `end_user_suspended`, and your account is flagged when one of your users is suspended or your users collect 10 strikes in one UTC day. Without `user`, the account is flagged at 3 and suspended at 10. Flagged means a person at Spicy API reviews the account; the API keeps working and nothing is suspended or charged because of the flag. `POST /v1/moderations` never records a strike. Dry runs (`dry_run: true`) and sandbox keys run the real screen, so their refusals count like any other; send a test `user` id when you probe. Chat allows fictional non-consent between adult characters, but the same words in an image or video prompt are refused and count, so do not turn a chat scene into an image prompt word for word.
- **Acceptable use sections that matter most here:** 2.1, 2.2 (chat may depict fictional non-consent between adults; the same words as an image prompt are refused and count), 2.3, 3.1 (no real people in selfies), 4 (age checks: where required, self-declaration is not enough), 6 (filtering, reports within 24 hours, labels). Full policy: https://www.spicyapi.com/acceptable-use. How screening, strikes and flags work: https://www.spicyapi.com/moderation.

## Getting started

1. Sign in at https://www.spicyapi.com/auth and accept the terms; the Default key is at https://www.spicyapi.com/dashboard/api-keys.
2. Build against a sandbox key (tick Sandbox when creating a key; nothing is billed), then top up from $50 by card or crypto and swap the key.
3. Set a low-balance email alert and a monthly spend limit in the dashboard.

Reply, send a selfie as a document (keeps the AI label) and read a voice note, over the plain Bot API (Python):

```python
import os, requests
from openai import OpenAI

spicy = OpenAI(api_key=os.environ["SPICYAPI_KEY"], base_url="https://api.spicyapi.com/v1")
TG = f"https://api.telegram.org/bot{os.environ['TELEGRAM_TOKEN']}"
PERSONA = {"role": "system", "content": "You are Ivy, 26, the user's flirty girlfriend. Short, warm replies."}

def chat(chat_id: int, tg_user: int, history: list) -> None:
    out = spicy.chat.completions.create(model="spicy-companion-1-flash", messages=[PERSONA, *history],
                                        user=f"tg_{tg_user}", max_tokens=300,
                                        extra_body={"safety_mode": "companion"})
    requests.post(f"{TG}/sendMessage", json={"chat_id": chat_id, "text": out.choices[0].message.content})

def selfie(chat_id: int, tg_user: int, ivy: str, scene: str) -> None:
    img = spicy.images.generate(model="spicy-image-1", prompt=scene, user=f"tg_{tg_user}",
                                extra_body={"character": ivy})
    url = img.data[0].url
    requests.post(f"{TG}/sendDocument", data={"chat_id": chat_id, "caption": "AI-generated"},
                  files={"document": (url.rsplit("/", 1)[-1], requests.get(url).content)})  # bytes, not our URL

def voice_note_text(ogg_bytes: bytes) -> str:
    return spicy.audio.transcriptions.create(model="spicy-transcribe-1", file=("note.ogg", ogg_bytes)).text
```

Other business types: https://www.spicyapi.com/docs/guides. Everything in one file: https://www.spicyapi.com/llms-full.txt. Questions: contact@spicyapi.com.
