🌍 Astrocartography API and ✋ Palmistry API are now live. Ship them in your app today.Get Started
Blog/AI

Why ChatGPT can't calculate your birth chart

A language model can name your Sun sign, because a birth date points to one. Everything else in a chart must be computed. Computing is the one thing a text predictor never does.

July 17, 2026·11 min read·AstrologyAPI Team
In brief
  • Language models predict text. They never run an ephemeris (a table of planet positions), so every planet position they give you is a guess.
  • Sun signs are the one exception. A birth date points to a Sun sign, so the answer can be found in text.
  • Moon sign, rising sign, house cusps, and dasha dates need more. They need an exact birth time and place, converted to Universal Time, plus an ephemeris. Vedic charts also need a chosen ayanamsha.
  • One hour of birth-time error moves the rising sign through most of a sign. It also shifts a twenty-year Vimshottari period by about ten months.
  • The fix is a design choice. Compute the chart with an API. Hand the JSON to the model as a tool result. Let it explain only what it received.

ChatGPT gets your Sun sign right almost every time. Your Moon sign comes back right about half the time. Your rising sign, house cusps, and dasha dates (the timed planet periods in Vedic astrology) are made up.

That is not a bug. A language model predicts text. It does not calculate where the planets were when you were born.

If you are building an astrology feature on an LLM (a large language model), get this one thing right. This post explains the problem, shows the math, and gives you the fix.

A newer model will not close this gap. The gap is not about model quality. It is about what the software does. A birth chart is a map of the sky at your birth. To make one, you do math on an ephemeris, a big table of planet positions. A language model predicts text instead. It can name your Sun sign because your birth date points to it. It makes up everything that needs real math.

The line between the two is sharp. Know where it falls, and you can build an astrology product people trust. Miss it, and users quietly stop opening your app.

What the model gets right

Sun signs come straight from the date. The Sun moves a little under one degree a day. It takes about thirty days to cross one thirty-degree sign. So the same dates map to the same Sun sign every year. And that mapping is written down in millions of pages on the web.

A model trained on those pages has seen “born 14 March” next to “Pisces” many times. Repeating that pattern is the one thing next-word prediction does best.

The model handles the edge case well too. Ask about a birth on 20 April. A good model will say the Aries and Taurus line can shift by up to a day, depending on the year. That is true, and it is also written down.

Then the same confident voice tells you the Moon was in Scorpio at eleven degrees. Nothing about the delivery changes. There is no drop in confidence. There is no note saying the number was a guess.

Inside the model, both answers are the same kind of thing. Both are text that fits the prompt. But only one can be found in text. The other needs a calculation the model never ran.

That even tone is the trap. Users test the Sun sign, see that it is right, and then trust everything after it. Here is where the line really falls.

Chart elementDetermined byRecoverable from text alone?
Sun signDate, plus the year near a cuspYes, it is a lookup
Moon signDate and time, to the hourNo
Ascendant (rising sign)Date, time to the minute, latitude and longitudeNo
House cuspsAscendant, plus a chosen house systemNo
Nakshatra and padaMoon longitude, to arcminutesNo
Dasha start and end datesMoon longitude at birthNo

What it takes to compute a chart

To place one planet at one moment, you need four things. A language model has none of them.

  • An ephemeris. The standard one is the Swiss Ephemeris, which Astrodienst publishes for exactly this use. It is built from NASA JPL's DE-series planetary data. It matches the JPL positions to within 0.001 arcseconds, across a range from 13201 BC to AD 17191. That table comes from heavy math on how the solar system moves, shipped as data files. You do not remember a table like that. You read it, and you fill in values between its rows.
  • The birth moment in Universal Time. The chart math runs in Universal Time. So you must convert the local birth time using the clock offset in force at that place on that date. That means old timezone borders, wartime clock changes, and daylight saving rules that changed year by year. Sometimes the rules changed town by town. India moved the whole country to one half-hour offset. Parts of the United States joined and left DST for decades. One wrong offset shifts everything after it. That is why timezone handling is the most common bug in astrology software.
  • A zero point for the zodiac. Western charts measure from the March equinox. Vedic charts measure from the fixed stars. The gap between the two systems is called the ayanamsha, and it is measured in degrees. Today it sits near twenty-four degrees, and it grows slowly over time. It is also not one agreed number. Our ayanamsha endpoint (one URL in our API) returns six named conventions for the same instant. For one sample birth they span 2.33 degrees, from 22.6552° under Raman to 24.9846° under Fagan-Bradley. A planet inside that band lands in a different sign depending on which one you pick.
  • A house division rule. Once you have the ascendant (the rising sign), you still have to cut the zodiac circle into twelve houses. There is more than one way to cut it. Placidus, Koch, Porphyry, equal house, whole sign, and Campanus all give different cusps for the same chart. The house a planet sits in comes from that choice. It is not a fact about the sky.

None of this can be pulled out of text. Every item is a calculation. And every calculation needs an exact time and an exact place.

How a small error grows

A made-up position is not close to right. It is simply wrong. The reason is speed. Some parts of a chart move fast.

The ascendant

The whole zodiac rises past the eastern horizon once for each turn of the Earth. Twelve signs in about twenty-four hours means about one sign every two hours.

That average hides a lot. At high latitudes, some signs rise in under an hour, while others take three hours or more. At 52°N, the ascendant can cross two sign borders inside a single hour.

Give a model a birth date with no time, and it cannot make a small error on the rising sign. It is picking one sign out of twelve.

The Moon

The Moon moves about half a degree an hour, or thirteen degrees a day. It crosses a thirty-degree sign in a little over two days. It changes sign twelve times in each twenty-seven-day sidereal month (one full lap around the zodiac). That works out to a sign change on about two days out of every five.

On those days, the date alone does not tell you the Moon sign. The hour of birth does. That is why the Moon sign is the value models get wrong most often. The right answer lives only inside a calculation the model never ran.

Dasha timelines

Vimshottari dasha timing starts from the Moon. The length of your first period depends on how far the Moon had moved into its nakshatra at birth. A nakshatra is one of the 27 Moon segments of the Vedic zodiac. Each one spans 13°20′, which is 800 arcminutes. One hour of clock error moves the Moon about 33 arcminutes. That is one twenty-fourth of a nakshatra.

Now carry that error into a twenty-year Venus period. It becomes a shift of about ten months. And every period border after it moves by the same amount. A model that guesses the Moon's position is not making a dasha table that runs a bit early. It is making a timeline for a different life.

A planet's position comes from a calculation. A model with no ephemeris cannot recall your Moon sign. It can only guess an answer that looks right.

Wrong answers that look right

A wrong answer that shows an error message becomes a support ticket. A wrong answer that sounds right just loses the user.

Astrology users catch this faster than most people, because many already know their own chart. Say someone knows they are Cancer rising, and your app tells them they are Libra rising. They do not file a bug report. They decide the product does not work, and they stop opening it.

Nothing shows up on your side. There is no error to log and no failed request to count. The request worked, the model answered, and the user left.

There is a second problem. The answers are not stable. Ask twice, and the same birth data can come back with two different Moon signs. That gets worse when people are paying you.

This problem spreads. Transit alerts follow where the planets are right now. Match scores compare two charts. Dasha alerts follow a timeline. All of these assume the birth chart is a fixed object. A chart made of generated text is not one.

The fix: compute first, then interpret

A better prompt will not fix this. You can tell a model to “be accurate” or to “only use real ephemeris data”. That only changes the text it writes about accuracy. It does not hand the model an ephemeris.

So split the work. The model handles language. An API (a web service your code can call) handles the math. The model reads the question and writes the chart reading. It never produces an astrological number of its own.

This pattern is called grounding. It means every number in the answer comes from a real calculation.

Tool use is the standard way to do this. With Anthropic's API, the model returns a structured tool_use block. Your code runs the call. You hand the output back as a tool_result block, and the model continues. The model decides when a tool applies. Your code decides what the tool does. Every major provider now has some version of this loop.

Server-side only
The API at json.astrologyapi.com sends no CORS headers, so calls made straight from the browser fail. Route every call through your own server, and keep the API key out of client-side code.
Grounded chart interpretationjavascript
import Anthropic from '@anthropic-ai/sdk'

const anthropic = new Anthropic()

const SYSTEM_PROMPT =
  'Base every astrological claim only on fields present in the tool ' +
  'result. If a value you need is not there, say so explicitly instead ' +
  'of estimating it.'

// Does the actual computation. The API uses HTTP Basic auth (user ID as
// the username, API key as the password) and sends no CORS headers, so
// this runs on your server, never in the browser.
async function getPlanetPositions(input) {
  const auth = Buffer.from(
    process.env.ASTRO_USER_ID + ':' + process.env.ASTRO_API_KEY,
  ).toString('base64')

  const res = await fetch('https://json.astrologyapi.com/v1/planets', {
    method: 'POST',
    headers: {
      Authorization: 'Basic ' + auth,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(input),
  })

  if (!res.ok) {
    throw new Error('AstrologyAPI returned ' + res.status)
  }

  return res.json()
}

const tools = [
  {
    name: 'get_planet_positions',
    description:
      'Returns computed positions for the Sun through Ketu, plus the ' +
      'ascendant, for a birth date, time, and place. Each entry includes ' +
      'its zodiac sign, nakshatra, house, and whether it is retrograde. ' +
      'These are ephemeris calculations, not an interpretation. Call this ' +
      'before making any astrological claim about a planet.',
    input_schema: {
      type: 'object',
      properties: {
        day: { type: 'integer', description: 'Day of birth, e.g. 10' },
        month: { type: 'integer', description: 'Month of birth, e.g. 5' },
        year: { type: 'integer', description: 'Year of birth, e.g. 1990' },
        hour: { type: 'integer', description: 'Hour of birth (24h), e.g. 19' },
        min: { type: 'integer', description: 'Minute of birth, e.g. 55' },
        lat: { type: 'number', description: 'Latitude, e.g. 19.2056' },
        lon: { type: 'number', description: 'Longitude, e.g. 25.2056' },
        tzone: { type: 'number', description: 'UTC offset, e.g. 5.5' },
      },
      required: ['day', 'month', 'year', 'hour', 'min', 'lat', 'lon', 'tzone'],
    },
  },
]

const messages = [
  {
    role: 'user',
    content:
      'What sign and nakshatra is the Moon in for a birth on 10 May 1990 ' +
      'at 19:55, latitude 19.2056, longitude 25.2056, timezone 5.5?',
  },
]

let response = await anthropic.messages.create({
  model: 'claude-sonnet-5',
  max_tokens: 1024,
  system: SYSTEM_PROMPT,
  tools,
  messages,
})

// The model asks for the tool; your code runs it and hands the JSON back
// as a tool_result. Repeat until the model stops asking.
while (response.stop_reason === 'tool_use') {
  const toolUse = response.content.find((block) => block.type === 'tool_use')
  const result = await getPlanetPositions(toolUse.input)

  messages.push({ role: 'assistant', content: response.content })
  messages.push({
    role: 'user',
    content: [
      {
        type: 'tool_result',
        tool_use_id: toolUse.id,
        content: JSON.stringify(result),
      },
    ],
  })

  response = await anthropic.messages.create({
    model: 'claude-sonnet-5',
    max_tokens: 1024,
    system: SYSTEM_PROMPT,
    tools,
    messages,
  })
}

const answer = response.content
  .filter((block) => block.type === 'text')
  .map((block) => block.text)
  .join('')

console.log(answer)

Three details in that example make it work.

First, look at the tool description. It says these are ephemeris calculations rather than an interpretation. It also tells the model to call the tool before it makes any claim about a planet. A model picks tools based on their descriptions. So that sentence matters.

Second, the system prompt limits the model to the fields in the tool result. It also tells the model to name any value it wanted but did not get. Without that rule, a model fills the gap with something that looks right.

Third, the loop appends the full response.content, with its tool_use block, before it appends the matching tool_result. Drop that step, and the pairing breaks. The next request then fails.

The response itself is a plain array, with one object per planet. Every value the reading depends on is present and named. That is what makes “use only these fields” a rule you can enforce:

One entry from the /planets responsejson
{
  "id": 0,
  "name": "Sun",
  "fullDegree": 72.18954079246434,
  "normDegree": 12.189540792464342,
  "sign": "Gemini",
  "signLord": "Mercury",
  "nakshatra": "Ardra",
  "nakshatraLord": "Rahu",
  "nakshatra_pad": 2,
  "house": 9,
  "isRetro": "false"
}

Look at fullDegree, carried to fourteen decimal places. That much precision is the clue. It is the output of a real calculation. A text predictor has no way to produce it.

Computed charts also cache well. Positions for a fixed birth date, time, and place never change. So store each result under that date, time, and place. Every follow-up question then reads from the stored copy instead of calling the API again.

The full working version, with the prompt guardrail and caching notes, is in the guides.

Start building with real ephemeris data
150 free credits. No card required.

MCP and the Chat API

Tool calling is the general form. If you would rather not run the loop yourself, there are two shorter routes.

Our MCP server offers the same calculations over the Model Context Protocol. Claude Desktop, Claude Code, Cursor, and many agent clients already support it. Register the server, and the client handles the rest.

The Chat API hosts the whole flow. You send a question and the birth details. You get back a reading that was grounded before it was written. You write no tool-calling code at all.

Underneath, all three work the same way. They compute the chart before the model writes a word. The only difference is how much of the code you own.

How to test whether a chatbot is grounded

Three checks separate a system that computes from one that generates. None of them needs access to the code. You can run them on your own product or on anyone else's.

Ask the same question twice, in separate sessions. A computed position never changes. The same birth data returns the same Moon sign and the same degree every time. Generated values drift. If two runs disagree, you have your answer.

Move the birth time by ninety minutes and ask again. The rising sign should change, usually by a whole sign. The Moon should move by about fifty arcminutes. Some systems return the same rising sign both times. Some move it in a way that ignores the clock. Neither kind is doing any calculation.

Ask for something the chart data cannot contain. Request a value that no endpoint returns, or a planet the tool does not cover. A grounded system says the field is missing. A generating one answers smoothly. That smooth answer tells you where every other answer came from.

What grounding doesn't fix

Grounding fixes one thing. It makes the numbers real. The other problems remain, and they are worth naming. A team that expects tool use to fix more will be surprised later.

Models still overstate. Hand one a correct chart, and it may write that a Saturn return “will” bring a career change. The position is computed, but the prediction is still just the model talking. No amount of tool use changes that. If your product needs careful wording, that is a prompt and content problem. It is a separate problem to solve.

Grounding also inherits your inputs. A chart computed from a wrong birth time is still wrong. It is just wrong with more precision. In some ways that is worse. The confident answer now rests on a real calculation that ran on bad data.

Some users cannot give you a reliable birth time. For those profiles, decide what to show and what to hold back. Do not just use noon and stay silent about it.

Correct data can still be misread. A model may say a planet is retrograde (moving backward in the sky) when isRetro came back "false". It may attach a nakshatra to the wrong planet. Grounding shrinks the failure from inventing to misreading. That is a big improvement, but it is not a guarantee.

Where accuracy matters, show the computed values in your interface, right next to the prose. Readers should be able to see the numbers the reading was built from.

That last point sums up the whole article. Language models are good at the language part of astrology. Keep them away from the math part, and let users see the numbers.

Frequently asked questions

Is ChatGPT accurate for astrology?

For Sun signs, usually, because a birth date points to one. For Moon sign, rising sign, house cusps, and dasha periods, no. Those need an ephemeris plus an exact birth time and place. A language model has no ephemeris. It gives you a number that looks likely, and it never runs the math.

Can AI generate a birth chart?

An AI product can, if it calls a calculation service. The model alone cannot. Making a chart means converting the birth moment to Universal Time, reading planet positions from an ephemeris, and dividing the houses. Give a model those numbers as a tool result, and it can explain them correctly.

What is a grounded astrology chatbot?

A chatbot where every number comes from a calculation the model did not run. The model gets the chart as structured data, uses only the fields in it, and says so when a value is missing. Grounding removes made-up positions. The reading built on them can still overstate.

Does AstrologyAPI work with ChatGPT and Claude?

Yes. The API is a plain HTTP JSON service, so it fits into any tool-calling loop. There is also an MCP server for clients that use the Model Context Protocol. If you would rather not write the tool-calling layer at all, the hosted Chat API does that part for you.

Sources
  1. 01Swiss Ephemeris overview (Astrodienst)
  2. 02Tool use with Claude (Anthropic documentation)
Further reading
Start building with real ephemeris data
150 free credits. No card required.
Related