Ship a working /horoscope, /tarot, or /lifepath bot on Telegram in about 15 minutes. One token from BotFather, one RoxyAPI key, two screens of code.
The Bot API is plain JSON over HTTPS, BotFather hands you a token in three messages, and the handler runs anywhere that can speak HTTPS, so there is very little between you and a working bot. RoxyAPI supplies the answers: 261+ endpoints across 18+ domains on one key, and a Remote MCP server per domain when you want a model to choose the call itself.
Pick the path first
Two shapes, and the choice decides everything below.
You want
Build
RoxyAPI path
/horoscope aries returns the same reading every time
A command bot
One endpoint per command, called from your handler
A chat that answers "what does my chart say about my career?"
A command bot is cheaper, deterministic, and needs no model. An AI bot does not need you to wire endpoints at all: point the model at https://roxyapi.com/mcp/{domain} and it reads the tool catalog itself. Start at the command bot below, then add the MCP path when free text arrives.
Step 1: prove the call
Before Telegram is involved at all, confirm your key works. Get your API key, then:
A 200 returns clean JSON with sign, date, overview, love, career, luckyNumber, moonSign, moonPhase, and energyRating, plus column, the whole reading as one ready-to-send block of 120 to 180 words. Errors come back as { error, code, doc_url }. Nothing is wrapped in a success envelope.
column is the fastest path to a good-looking Telegram reply: one field, already written as prose, well inside the 4096 character message cap. The six topic fields (overview, love, career, health, finance, advice) read the same dated events filtered to their own life area, each written as a column of its own, so render one shape or the other, never both.
Step 2: register the bot and pick a transport
Open @BotFather, send /newbot, pick a name and a username ending in bot. You get an HTTP token like 12345:ABC.... That token authenticates every call your handler makes back to Telegram, so treat it like a password.
Your handler loops on getUpdates, processes each message, replies. No webhook, no HTTPS host, no signing. Run it on a laptop, in a container, during a demo.
Use it for prototyping and low volume. Move to a webhook when you deploy.
Telegram POSTs every update to an HTTPS URL you own. Lower latency, scales horizontally. Set a secret_token at the same time so your endpoint can prove the request came from Telegram.
Telegram then sends that value in the X-Telegram-Bot-Api-Secret-Token header on every POST. Compare it and reject a mismatch. The URL needs a valid certificate on port 443, 80, 88, or 8443; use ngrok locally. Setting a webhook disables getUpdates, so pick one transport, not both.
Step 3: the /horoscope handler
The examples run behind the webhook from Step 2. The body of each handler works unchanged inside a long-polling loop.
# pip install fastapi uvicorn httpx roxy-sdk
import os
import httpx
from fastapi import FastAPI, Request, Response
from roxy_sdk import create_roxy, RoxyAPIError
roxy = create_roxy(os.environ['ROXY_API_KEY'])
TG = f"https://api.telegram.org/bot{os.environ['TELEGRAM_BOT_TOKEN']}"
SECRET = os.environ['TELEGRAM_WEBHOOK_SECRET']
app = FastAPI()
@app.post('/webhook')
async def webhook(request: Request):
if request.headers.get('X-Telegram-Bot-Api-Secret-Token') != SECRET:
return Response(status_code=403)
update = await request.json()
msg = update.get('message')
if not msg or 'text' not in msg:
return {'ok': True}
parts = msg['text'].split()
if parts and parts[0] == '/horoscope' and len(parts) > 1:
sign = parts[1].lower()
try:
data = await roxy.astrology.get_daily_horoscope_async(sign=sign)
reply = (
f"{data['sign']} for {data['date']}\n\n"
f"{data['column']}\n\n"
f"Lucky number: {data['luckyNumber']}\n"
f"Moon: {data['moonSign']} ({data['moonPhase']})\n"
f"Energy: {data['energyRating']}/10"
)
except RoxyAPIError as e:
reply = f"Could not read {sign}: {e.code}"
async with httpx.AsyncClient() as client:
await client.post(
f"{TG}/sendMessage",
json={'chat_id': msg['chat']['id'], 'text': reply},
)
return {'ok': True}
# uvicorn telegram_bot:app --reload --port 3000
Send /horoscope aries to the bot. The reading comes back within a second.
Add /tarot with the card image
POST /tarot/daily returns card.imageUrl, a public CDN URL, and Telegram sendPhoto accepts a remote URL directly, so there is nothing to host. Passing seed makes the draw reproducible: the same seed on the same date always returns the same card, so a user who asks ten times before midnight sees one card.
Drop this inside the /webhook handler above; it reuses the same roxy, msg, and TG.
Any chart, panchang, dasha, synastry or compatibility call needs latitude, longitude, and timezone. Resolve them from the city with GET /location/search (roxy.location.searchCities in TypeScript, roxy.location.search_cities in Python) and pass the IANA timezone straight through. The server resolves DST for the chart date, so a January birth in New York gets EST and a July birth gets EDT.
Add an AI bot on Remote MCP
Hardcoded commands stop at the questions you predicted. For free text, connect the model to a Remote MCP server and let it choose.
The servers run at https://roxyapi.com/mcp/{domain} over Streamable HTTP, POST only. No stdio, no Docker, no local process. Authenticate with the X-API-Key header or Authorization: Bearer. The model calls tools/list, sees the typed catalog with field descriptions and enums, picks a tool, and calls it. You wire no endpoints. In the handler you forward msg.text to your agent and send the answer back with sendMessage.
Tool discovery is free. Each tools/call counts as one request, the same as the equivalent REST call. Full setup, per-client configs, and the compact argument that cuts agent token cost are on the Remote MCP page. If you would rather have a coding agent build the whole bot, the prompts page hands it the truth sources first.
Frequently asked questions
How do I keep group chats from burning quota?
Long polling and webhooks both deliver every message in a group your bot has been added to. Filter on a leading / so only commands reach RoxyAPI, and cache daily content per user in a short-lived store so ten /horoscope aries in a minute cost one call. Sending /setprivacy to BotFather turns on Privacy Mode, after which the bot only sees commands and replies.
Can the bot reply in another language?
Yes. Most domains take a lang query parameter: en, tr, de, es, hi, pt, fr, ru, zh-Hans, zh-Hant. Pass query: { lang: 'hi' } in TypeScript or lang='hi' in Python. Telegram passes an IETF tag in msg.from.language_code, so you can map it and forward. Machine values such as sign names stay canonical; the prose translates in place.
What happens on a malformed birth date?
RoxyAPI returns a 400 with code: validation_error and an issues[] array naming every field problem at once. Catch it (if (error) in TypeScript, except RoxyAPIError as e in Python), reply with something readable, and log the raw error. Retry 429 with code rate_limit_per_minute and 503 with code compute_saturated after the Retry-After seconds. Retry 500 with code internal_error with exponential backoff. A request that fails on our side with a 5xx does not count against your monthly allowance, so retrying it is free. A 429 with code rate_limit_exceeded means the monthly allowance is used up: retrying cannot help before the reset, and Retry-After gives the seconds to it. Fix the request for any other 4xx, because it will fail the same way until it changes. Every official SDK already retries these for you, twice and honouring Retry-After, so a call made through an SDK needs no second retry loop.
Do I have to verify webhook payloads?
Telegram does not sign the body. The defense is the secret_token you pass to setWebhook, echoed back in the X-Telegram-Bot-Api-Secret-Token header on every request, as the handlers above check. Without it, anyone who guesses your URL can post updates to it.
Can I use both polling and a webhook?
No. Setting a webhook disables getUpdates. Call deleteWebhook to go back to polling.
Gotchas
Backend-only key. The RoxyAPI key and the bot token live in your handler. Never echo either into a chat and never commit one.
Chat IDs are integers. JSON handles both forms, but typed code in TypeScript or Pydantic should match.
text is missing on photo, sticker and location messages. Guard before parsing commands.
A message caps at 4096 characters, a photo caption at 1024. A daily column fits both; a full natal chart interpretation fits neither, so split it.
Photos sent by URL cap at 5 MB. Upload as multipart if you generate something larger.
Formatting needs parse_mode. Add 'HTML' or 'MarkdownV2' to sendMessage to bold the sign. Without it everything is plain text, and MarkdownV2 needs its reserved characters escaped.
Rate limits are per chat and in bulk. About one message per second to the same chat, about 30 per second across different chats. A blast to a thousand subscribers needs pacing.
Prefer the IANA timezone."Europe/London" beats 5.5, because the server resolves DST for the chart date.
Tool discovery is free, tool calls are not. Debugging an agent against tools/list costs nothing; every tools/call is one request.
The API reference has every endpoint across 18+ domains.
R
RosieYour AI guide to RoxyAPI
R
Hi, I am Rosie, your guide to the RoxyAPI insight tools.
Ask me to read your birth chart, pull a card, or map your numbers, and I will interpret what it means for you. Unlike most AIs, I never make the numbers up: every reading runs on real, verified calculations. Curious about the API, pricing, or docs instead? Ask me that too.