# 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](/account).
2. An LLM key from [Anthropic](https://console.anthropic.com/), [OpenAI](https://platform.openai.com/api-keys), or [Google AI Studio](https://aistudio.google.com/app/apikey).
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](/starters).

```bash
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:

```bash
npm run dev
```

Open [http://localhost:3000](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:

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

Create `chat.ts`:

```typescript
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:

```bash
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."


### Python variant
```python
# 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')
```

### PHP variant
```php
<?php

use function RoxyAPI\Sdk\createRoxy;

$roxy = createRoxy(getenv('ROXY_API_KEY'));

function callRoxy($roxy, $name, $args) {
    if ($name === 'get_daily_horoscope') {
        return $roxy->astrology->getDailyHoroscope(sign: $args['sign']);
    }
    if ($name === 'generate_natal_chart') {
        return $roxy->astrology->generateNatalChart(...$args);
    }
    if ($name === 'generate_bodygraph') {
        return $roxy->humanDesign->generateBodygraph(...$args);
    }
    if ($name === 'generate_timeline') {
        return $roxy->forecast->generateTimeline(...$args);
    }
    throw new Exception('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:

```bash
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](/docs/mcp) 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:

```html
<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](/docs/ui) for the full catalog.

## Ready-made starter

Browse [/starters/astrology-ai-chatbot](/starters) 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](/docs/guides/function-calling) for the Gemini variant.

## What to build next

- The [MCP setup docs](/docs/mcp) cover Claude Desktop, Cursor, Antigravity, and custom Streamable HTTP clients.
- The [function-calling guide](/docs/guides/function-calling) shows the raw tool-calling pattern for OpenAI, Anthropic, and Gemini.
- The [Human Design guide](/docs/guides/human-design) and [forecast guide](/docs/guides/forecast) cover the new May 2026 domains your bot can answer for.
- The [personalized tracker tutorial](/docs/tutorials/personalized-tracker) shows the full server-side build for HD + forecast + numerology in a single dashboard.
- The [starters page](/docs/starters) lists the full catalog of clone-and-ship apps.
