- Docs
- Build With RoxyAPI
- AI Readings
AI astrology readings with any LLM
To generate an AI astrology reading, call a RoxyAPI endpoint for the calculated chart and its interpretation text, then pass that JSON to any large language model with a short style brief. The API supplies the facts, verified against NASA JPL Horizons. The model supplies the voice, the length and the synthesis.
This split is what keeps a reading honest. A model asked to read a chart from memory invents placements; a model handed the chart writes about the chart it was given. Every interpretive RoxyAPI endpoint returns calculated values and interpretation text in the same response, translated with ?lang=, so the model never has to calculate or translate an astrological fact. The code below uses Gemini because the pattern needs nothing model-specific. Swap the client for OpenAI, Anthropic, Mistral or a local model and every line that touches RoxyAPI stays the same.
Three ways to connect a model
| Path | Who picks the endpoint | Best for |
|---|---|---|
| Direct | Your code | Written readings, reports, emails, anything with a fixed shape. One API call, one model call, predictable cost |
| Function calling | The model, from tools you define | Chat where the user asks open questions, with a tool list you control |
| Remote MCP | The model, from every tool on a domain server | Agents that should reach the whole domain with no tool schemas to maintain |
Start with Direct. It is the cheapest path, it is cacheable, and it is what most published readings are.
The pattern in code
Set ROXY_API_KEY (get your API key) and GEMINI_API_KEY, then npm install @google/genai. Keep both keys on your server.
// reading.mjs: one helper for every domain
import { GoogleGenAI } from '@google/genai';
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
export async function roxy(path, body, lang = 'en') {
const res = await fetch(`https://roxyapi.com/api/v2${path}?lang=${lang}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.ROXY_API_KEY },
body: JSON.stringify(body),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.code}: ${data.error}`);
return data;
}
export async function interpret(data, brief, lang = 'en') {
const { text } = await ai.models.generateContent({
model: 'gemini-flash-latest',
contents: JSON.stringify(data),
config: {
systemInstruction: [
'You write readings for a professional astrology product.',
'Every claim must trace to a value in the JSON. Build on its interpretation text, never invent placements, cards or dates.',
`Write in language "${lang}". ${brief}`,
].join(' '),
},
});
return text;
}
import { roxy, interpret } from './reading.mjs';
const me = { date: '1990-07-15', time: '14:30:00', latitude: 40.7128, longitude: -74.006, timezone: 'America/New_York' };
const chart = await roxy('/astrology/natal-chart', me);
console.log(await interpret(chart, 'Open with the Sun, Moon and Ascendant together, then the strongest aspects. 600 words of prose.'));
import { GoogleGenAI } from '@google/genai';
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const getNatalChart = {
name: 'get_natal_chart',
description: 'Western natal chart with planets, houses, aspects, patterns and interpretation text for each.',
parametersJsonSchema: {
type: 'object',
properties: {
date: { type: 'string', description: 'Birth date, YYYY-MM-DD' },
time: { type: 'string', description: 'Birth time, HH:MM:SS, 24 hour' },
latitude: { type: 'number' },
longitude: { type: 'number' },
timezone: { type: 'string', description: 'IANA zone, for example Europe/Istanbul' },
},
required: ['date', 'time', 'latitude', 'longitude', 'timezone'],
},
};
async function callRoxy(args) {
const res = await fetch('https://roxyapi.com/api/v2/astrology/natal-chart', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': process.env.ROXY_API_KEY },
body: JSON.stringify(args),
});
return res.json();
}
const chat = ai.chats.create({
model: 'gemini-flash-latest',
config: {
tools: [{ functionDeclarations: [getNatalChart] }],
systemInstruction: 'You are a professional astrologer. For any chart question, call get_natal_chart and read from its result. Never invent placements.',
},
});
let response = await chat.sendMessage({
message: 'I was born on 15 July 1990 at 14:30 in Istanbul. What does my Moon say about me?',
});
while (response.functionCalls?.length) {
const parts = [];
for (const call of response.functionCalls) {
parts.push({ functionResponse: { id: call.id, name: call.name, response: await callRoxy(call.args) } });
}
response = await chat.sendMessage({ message: parts });
}
console.log(response.text);
The function calling guide covers the same loop for OpenAI and Anthropic.
// npm install @google/genai @modelcontextprotocol/sdk
import { GoogleGenAI, mcpToTool } from '@google/genai';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
const mcp = new Client({ name: 'my-reading-app', version: '1.0.0' });
await mcp.connect(
new StreamableHTTPClientTransport(new URL('https://roxyapi.com/mcp/astrology'), {
requestInit: { headers: { 'X-API-Key': process.env.ROXY_API_KEY } },
})
);
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });
const response = await ai.models.generateContent({
model: 'gemini-flash-latest',
contents: 'I was born on 15 July 1990 at 14:30 in Istanbul (41.0082, 28.9784, Europe/Istanbul). Which transits are strongest for me today, and what should I do with them?',
config: {
tools: [mcpToTool(mcp)],
systemInstruction: `You are a professional astrologer. Today is ${new Date().toISOString().slice(0, 10)}. Always read placements and transits from the tools, never from memory.`,
},
});
console.log(response.text);
await mcp.close();
The model chose the transit tool, sent the birth data and wrote from the result, with no tool schema written by hand. Every domain has its own server: MCP setup.
Expand it: one helper, any domain
The Direct helper takes any endpoint and any brief, so a new product is two lines. Each of these runs against the helper above.
const partner = { date: '1992-03-02', time: '08:00:00', latitude: 51.5074, longitude: -0.1278, timezone: 'Europe/London' };
const synastry = await roxy('/astrology/synastry', { person1: me, person2: partner });
console.log(await interpret(synastry, 'A relationship reading for both people: what flows, where they will clash, one way to meet in the middle. 200 words.'));
const spread = await roxy('/tarot/spreads/three-card', { question: 'Should I change jobs this year?' });
console.log(await interpret(spread, 'Read the past, present and future cards as one story that answers the question. 150 words.'));
const digest = await roxy('/forecast/digest', { birthData: me });
console.log(await interpret(digest, 'A weekly forecast email: the three dates that matter most in the next seven days, each with one practical suggestion. 150 words.'));
const chart = await roxy('/astrology/natal-chart', me, 'tr');
console.log(await interpret(chart, 'A 300 word birth chart reading that opens with the Sun, Moon and Ascendant.', 'tr'));
The same two lines work for a Vedic kundli, a human design chart, a numerology profile or an I Ching cast. Find the endpoint in the API reference and write the brief your product needs.
What the model reads
Interpretive endpoints carry the facts and the meaning side by side, so the model can quote a degree and explain it in the same sentence.
- Natal chart: each planet has
interpretation.summaryandinterpretation.detailedfor that planet in its sign and house. Each aspect hasaspectInterpretation, beside itsorbandstrength.patternsnames T-squares, grand trines and stelliums with their own interpretation. - Transits: each transit has
summary,timing,impactandguidance, scored bystrength. - Forecast: each event has an exact
datetime, asignificancescore and adescription.
Human-readable text arrives in the language you request; machine values such as sign and type stay in English so your code can switch on them. See localization.
Show the chart beside the reading
The model writes the words; it should never draw the chart. Pass the same JSON to a @roxyapi/ui component and the reading arrives next to a real natal wheel, kundli or tarot spread. In chat, where the model picks the tool, map the tool name to its component and the result renders itself. That is generative UI, also called tool UI, and the AI chat widgets tutorial wires it for Gemini, OpenAI, Anthropic and the Vercel AI SDK.
Production notes
- Cache the reading, not just the chart. A birth chart never changes, so a reading for the same chart, language and brief can be stored and served again at no model cost.
- Pass less for shorter readings. A 150 word reading does not need all 14 bodies; send the fields the brief uses and the model reads fewer tokens.
- Pin the brief, version it, and review samples. The brief is your house style. Treat a brief change like a copy change.
- Keep the calculation in the API. Ask the model to explain, never to compute. Positions, aspects and dates from a model are guesses; from the API they are measured.
The model writes the words, so its output is yours to review. RoxyAPI guarantees the calculations and the interpretation text it returns; the prose a model builds on top depends on the model and the brief you give it.
FAQ
Does RoxyAPI generate AI readings itself?
No. RoxyAPI returns calculated charts with interpretation text for every placement, aspect and event, and your model writes the final reading. That keeps the voice, length and model choice in your product.
Which LLM works best for astrology readings?
Any current model with a long context window. The model does not calculate anything, so accuracy comes from the API data and the quality of the reading comes from the brief.
How do I stop an LLM from inventing astrology placements?
Give it the chart instead of asking it to calculate one. Pass the API response as the input and tell the model that every claim must trace to a value in that JSON.
Can I generate readings in Turkish, Spanish or Hindi?
Yes. Add ?lang= to the API call and the interpretation text arrives in that language, then ask the model to write in the same language.
Should I use MCP or function calling for AI astrology?
Use Remote MCP when an agent should reach every tool on a domain with no schemas to maintain. Use function calling when you want a short, fixed tool list. For a written report with a fixed shape, call the endpoint directly.
What to build next
- The AI chatbot tutorial wires these paths into a complete chat app.
- The AI companion with memory remembers each user between sessions.
- The AI chat widgets tutorial renders tool results as charts with generative UI.
- The MCP setup lists every domain server and client.
- The API reference shows every interpretive field with a sample response.