1. Docs
  2. What To Build
  3. AI Astrology Chatbot

Build an AI astrology chatbot

Ship a chatbot that answers "what is my Life Path", "draw me a tarot card", "what is my Human Design type", "what is my kundli" using real Roxy data. Time to ship: 30 to 45 minutes.

The flagship Roxy build. A user types a natural-language question. An LLM picks the right Roxy tool, calls it, and replies in plain English. The LLM does endpoint selection, field extraction, and interpretation synthesis. You write no question-to-endpoint mapping.

MCP (Model Context Protocol) is the fast path. Register Roxy once, every Roxy endpoint becomes a tool the model can auto-discover. The slower path is raw function calling, where you hand-define each tool. Both are covered below.

What you can build

  • Daily horoscope bot ("what is the Leo horoscope today")
  • Natal chart bot ("I was born July 15 1990 at 2:30 PM in New York, what is my chart")
  • Human Design coach ("what is my type, strategy, authority")
  • Daily forecast tracker ("what transits do I have this week")
  • Tarot reader ("pull me a card", "do a three-card spread")
  • Numerology assistant ("what is my Life Path for July 15 1990")
  • Vedic kundli bot ("generate the kundli for someone born July 15 1990 in Mumbai")
  • KP horary oracle ("when will my house sell, ruling planets now")
  • Multi-domain wellness companion covering all twelve Roxy domains

Prerequisites

  1. A Roxy API key from /account.
  2. An LLM key from Anthropic, OpenAI, or Google AI Studio.
  3. Node.js 18+ or Bun (for the from-scratch path) or any MCP-compatible client (for the MCP path).

Option A: clone the flagship starter

The fastest path. The MIT-licensed reference integration the Roxy team maintains, browse it at /starters/astrology-ai-chatbot.

git clone https://github.com/RoxyAPI/astrology-ai-chatbot.git
cd astrology-ai-chatbot
npm install

Create .env.local:

ROXY_API_KEY=your_roxy_key
ANTHROPIC_API_KEY=your_claude_key
# or OPENAI_API_KEY / GOOGLE_GENERATIVE_AI_API_KEY

Run:

npm run dev

Open http://localhost:3000. The starter ships with a web UI, conversation history, multi-provider LLM support, and auto-discovery of every Roxy MCP server (astrology, vedic-astrology, numerology, tarot, biorhythm, iching, crystals, dreams, angel-numbers, human-design, forecast, location). You do not define tools. The Vercel AI SDK picks them up from the remote MCP manifest.

Option B: build it from scratch with function calling

If you want to see the machinery, here is a single-file chatbot that hand-defines four tools and hand-calls Roxy via the SDK.

Setup:

mkdir astro-chat && cd astro-chat
npm init -y
npm install @anthropic-ai/sdk @roxyapi/sdk

Create chat.ts:

import Anthropic from '@anthropic-ai/sdk';
import { createRoxy } from '@roxyapi/sdk';
import { createInterface } from 'node:readline';

const client = new Anthropic();
const roxy = createRoxy(process.env.ROXY_API_KEY!);

const tools = [
  {
    name: 'get_daily_horoscope',
    description: 'Daily horoscope for a zodiac sign. Returns overview, love, career, health, finance.',
    input_schema: {
      type: 'object',
      properties: { sign: { type: 'string', description: 'Lowercase sign, aries through pisces' } },
      required: ['sign'],
    },
  },
  {
    name: 'generate_natal_chart',
    description: 'Western natal chart from birth data. Returns planets with interpretation, houses, aspects.',
    input_schema: {
      type: 'object',
      properties: {
        date: { type: 'string', description: 'YYYY-MM-DD' },
        time: { type: 'string', description: 'HH:MM:SS' },
        latitude: { type: 'number' },
        longitude: { type: 'number' },
        timezone: { type: 'string', description: 'IANA id, e.g. America/New_York' },
      },
      required: ['date', 'time', 'latitude', 'longitude', 'timezone'],
    },
  },
  {
    name: 'generate_bodygraph',
    description: 'Human Design bodygraph. Returns type (Manifestor, Generator, Manifesting Generator, Projector, Reflector), strategy, authority, profile, defined centers, channels, gates, incarnation cross.',
    input_schema: {
      type: 'object',
      properties: {
        date: { type: 'string' },
        time: { type: 'string' },
        timezone: { type: 'string' },
        latitude: { type: 'number' },
        longitude: { type: 'number' },
      },
      required: ['date', 'time', 'timezone'],
    },
  },
  {
    name: 'generate_forecast_timeline',
    description: 'Forecast timeline of significant astrological events (transit aspects, sign ingresses, retrograde stations, dasha changes, biorhythm critical days) between two dates for one user.',
    input_schema: {
      type: 'object',
      properties: {
        birthData: {
          type: 'object',
          properties: {
            date: { type: 'string' },
            time: { type: 'string' },
            timezone: { type: 'string' },
          },
          required: ['date', 'time', 'timezone'],
        },
        startDate: { type: 'string', description: 'YYYY-MM-DD' },
        endDate: { type: 'string', description: 'YYYY-MM-DD' },
      },
      required: ['birthData'],
    },
  },
  {
    name: 'calculate_life_path',
    description: 'Numerology Life Path number from birth date.',
    input_schema: {
      type: 'object',
      properties: {
        year: { type: 'number' },
        month: { type: 'number' },
        day: { type: 'number' },
      },
      required: ['year', 'month', 'day'],
    },
  },
  {
    name: 'get_daily_tarot_card',
    description: 'Draw a single tarot card with interpretation.',
    input_schema: { type: 'object', properties: { seed: { type: 'string' } } },
  },
];

async function callRoxy(name: string, input: any) {
  if (name === 'get_daily_horoscope') {
    const { data } = await roxy.astrology.getDailyHoroscope({ path: { sign: input.sign } });
    return data;
  }
  if (name === 'generate_natal_chart') {
    const { data } = await roxy.astrology.generateNatalChart({ body: input });
    return data;
  }
  if (name === 'generate_bodygraph') {
    const { data } = await roxy.humanDesign.generateBodygraph({ body: input });
    return data;
  }
  if (name === 'generate_forecast_timeline') {
    const { data } = await roxy.forecast.generateTimeline({ body: input });
    return data;
  }
  if (name === 'calculate_life_path') {
    const { data } = await roxy.numerology.calculateLifePath({ body: input });
    return data;
  }
  if (name === 'get_daily_tarot_card') {
    const { data } = await roxy.tarot.getDailyCard({ body: input });
    return data;
  }
  throw new Error('unknown tool');
}

async function chat(history: any[]) {
  let response = await client.messages.create({
    model: 'claude-sonnet-4-5-20250929',
    max_tokens: 1024,
    system: 'You are a friendly spiritual assistant. Always call a tool when the user asks for horoscope, birth chart, Human Design, forecast, tarot, or numerology data. Never guess. For any chart question, geocode the city first if you only have a name.',
    tools,
    messages: history,
  });

  while (response.stop_reason === 'tool_use') {
    const tu = response.content.find((b: any) => b.type === 'tool_use') as any;
    const result = await callRoxy(tu.name, tu.input);
    history.push({ role: 'assistant', content: response.content });
    history.push({
      role: 'user',
      content: [{ type: 'tool_result', tool_use_id: tu.id, content: JSON.stringify(result) }],
    });
    response = await client.messages.create({
      model: 'claude-sonnet-4-5-20250929',
      max_tokens: 1024,
      tools,
      messages: history,
    });
  }

  const text = (response.content.find((b: any) => b.type === 'text') as any)?.text ?? '';
  history.push({ role: 'assistant', content: response.content });
  return text;
}

const rl = createInterface({ input: process.stdin, output: process.stdout });
const history: any[] = [];
console.log('Roxy chatbot. Type quit to exit.\n');
const loop = () => rl.question('You: ', async (line) => {
  if (line.trim().toLowerCase() === 'quit') return rl.close();
  history.push({ role: 'user', content: line });
  const reply = await chat(history);
  console.log('\nBot: ' + reply + '\n');
  loop();
});
loop();

Run:

ROXY_API_KEY=your_roxy_key ANTHROPIC_API_KEY=your_claude_key bun chat.ts

Try:

  • "What is the Leo horoscope today?"
  • "I was born July 15 1990 at 2:30 PM in New York, what is my Human Design type?"
  • "Show me my forecast for the next 30 days."
  • "Draw me a tarot card."
  • "What is my Life Path? Birthday July 15 1990."
# chat.py, same idea with the Python SDK
import os
import anthropic
from roxy_sdk import create_roxy

client = anthropic.Anthropic()
roxy = create_roxy(os.environ['ROXY_API_KEY'])

def call_roxy(name, args):
    if name == 'get_daily_horoscope':
        return roxy.astrology.get_daily_horoscope(sign=args['sign'])
    if name == 'generate_natal_chart':
        return roxy.astrology.generate_natal_chart(**args)
    if name == 'generate_bodygraph':
        return roxy.human_design.generate_bodygraph(**args)
    if name == 'generate_timeline':
        return roxy.forecast.generate_timeline(**args)
    if name == 'calculate_life_path':
        return roxy.numerology.calculate_life_path(**args)
    if name == 'get_daily_tarot_card':
        return roxy.tarot.get_daily_card(**args)
    raise ValueError('unknown tool')

Option C: skip tool definitions with MCP

Neither of the above scales. The moment you want "all twelve domains, every endpoint" you stop hand-writing tool schemas and point the LLM at MCP.

Register all Roxy MCP servers with Claude Code in one go:

claude mcp add-json --scope user roxy-astrology     '{"type":"http","url":"https://roxyapi.com/mcp/astrology","headers":{"X-API-Key":"YOUR_KEY"}}'
claude mcp add-json --scope user roxy-vedic         '{"type":"http","url":"https://roxyapi.com/mcp/vedic-astrology","headers":{"X-API-Key":"YOUR_KEY"}}'
claude mcp add-json --scope user roxy-numerology    '{"type":"http","url":"https://roxyapi.com/mcp/numerology","headers":{"X-API-Key":"YOUR_KEY"}}'
claude mcp add-json --scope user roxy-tarot         '{"type":"http","url":"https://roxyapi.com/mcp/tarot","headers":{"X-API-Key":"YOUR_KEY"}}'
claude mcp add-json --scope user roxy-iching        '{"type":"http","url":"https://roxyapi.com/mcp/iching","headers":{"X-API-Key":"YOUR_KEY"}}'
claude mcp add-json --scope user roxy-biorhythm     '{"type":"http","url":"https://roxyapi.com/mcp/biorhythm","headers":{"X-API-Key":"YOUR_KEY"}}'
claude mcp add-json --scope user roxy-dreams        '{"type":"http","url":"https://roxyapi.com/mcp/dreams","headers":{"X-API-Key":"YOUR_KEY"}}'
claude mcp add-json --scope user roxy-crystals      '{"type":"http","url":"https://roxyapi.com/mcp/crystals","headers":{"X-API-Key":"YOUR_KEY"}}'
claude mcp add-json --scope user roxy-angel         '{"type":"http","url":"https://roxyapi.com/mcp/angel-numbers","headers":{"X-API-Key":"YOUR_KEY"}}'
claude mcp add-json --scope user roxy-human-design  '{"type":"http","url":"https://roxyapi.com/mcp/human-design","headers":{"X-API-Key":"YOUR_KEY"}}'
claude mcp add-json --scope user roxy-forecast      '{"type":"http","url":"https://roxyapi.com/mcp/forecast","headers":{"X-API-Key":"YOUR_KEY"}}'
claude mcp add-json --scope user roxy-location      '{"type":"http","url":"https://roxyapi.com/mcp/location","headers":{"X-API-Key":"YOUR_KEY"}}'

Same pattern for Claude Desktop, Cursor, Antigravity, and Vercel AI SDK. See the MCP setup docs for full client instructions.

Render the result (optional)

Combine the chatbot with <roxy-*> web components for a hybrid chat + visual UI. After the LLM replies with a natal-chart tool result, render the wheel:

<script src="https://cdn.jsdelivr.net/npm/@roxyapi/ui@0/dist/cdn/roxy-ui.js" defer></script>
<roxy-natal-chart id="chart"></roxy-natal-chart>
<roxy-bodygraph id="bg"></roxy-bodygraph>
<script type="module">
  document.getElementById('chart').data = lastNatalChartToolResult;
  document.getElementById('bg').data    = lastBodygraphToolResult;
</script>

See the UI components page for the full catalog.

Ready-made starter

Browse /starters/astrology-ai-chatbot for the deploy-ready Next.js build. Same idea as Option A above with the full UI, conversation persistence, and multi-MCP discovery.

Gotchas

  • Do not embed the Roxy API key in the browser. The chatbot backend holds the secret key and proxies calls. Anyone can view page source.
  • Timezone accepts IANA strings. The LLM naturally fills "America/New_York" from "New York"; let it. The server resolves DST for the birth date automatically. Decimal offsets like -5 also work but do not DST-correct.
  • Tell the model to geocode for chart calls. Add a line to the system prompt: "For any birth chart or Human Design question, call location search first to get latitude, longitude, and timezone." Without that, the LLM sometimes invents coordinates.
  • Seeded endpoints are deterministic. Pass seed: userId to daily tarot and the same user gets the same card all day. That is a feature, not a cache staleness bug.
  • Forecast endpoints accept birthData as a nested object. Not flat. Easy to flatten by mistake.
  • Gemini tool schemas use Type enums. See the function-calling guide for the Gemini variant.

What to build next