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

Guides

Ground an LLM on computed charts

Language models generate text by completing patterns from training data. They do not run ephemeris calculations. Ask a model for a planet's longitude, a house cusp degree, or a dasha start date.

The model returns a plausible-looking number, not a computed one. Every astrological value a model outputs must come from a computed source — this API — never from the model's own generation.

This guide shows how to wire a chat model to the AstrologyAPI. The model writes the interpretation, and the API does the arithmetic.

The failure mode

Ask a model directly for a natal chart: “what's my ascendant if I was born at 7pm on May 10, 1990 in Athens, Greece?”

The model answers with confidence and detail: a specific sign, a specific degree. The answer is very likely wrong.

The model never ran an ephemeris calculation. It has no way to know the number is wrong, and it will not tell you.

Two classes of output fail this way, and both are numeric, sensitive to the exact birth time and location, and impossible to pattern-match correctly:

  • Planet longitudes. A planet's position moves with the date and time. A model estimates it from surrounding text patterns, which drift by whole degrees or signs.
  • Dasha dates. A dasha start or end date derives from the Moon's exact longitude at birth. Off by a few minutes of birth time and the date shifts by months.

The architecture: LLM does the language, the API does the math

Split the work along its natural seam. The model's job is to understand the user's question and write the interpretation. The API's job is the arithmetic. The model never produces an astrological number of its own.

The flow is one loop:

  1. The user asks a question.
  2. Your code — or the model, through tool use — calls the AstrologyAPI with the birth details (planets, for example).
  3. The API returns computed values.
  4. The model writes its interpretation using only those returned values.
The API at json.astrologyapi.com does not send CORS headers, so browser-direct calls fail. Route every call through your server, and never place your API key in client-side code.

Worked example

This example uses the Anthropic TypeScript SDK. Install it with npm install @anthropic-ai/sdk. The example defines one tool, get_planet_positions, and runs a manual tool-use loop.

The model requests the tool, and your code runs the HTTP call to /planets. Your code hands the result back as a tool_result.

The model then writes its answer — sign, house, retrograde status — from those values instead of guessing at them.

The API uses HTTP Basic auth — your user ID as the username and your API key as the password. The example reads ANTHROPIC_API_KEY, ASTRO_USER_ID, and ASTRO_API_KEY from the environment.

This example throws on a non-200 response to stay focused. For retries and backoff, see the production checklist.

import Anthropic from '@anthropic-ai/sdk'

const client = new Anthropic()

const ASTRO_BASE = 'https://json.astrologyapi.com/v1'

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

// Does the actual computation: POST the birth details to the API over
// HTTP Basic auth and return the parsed JSON (an array, one entry per
// planet). The API rejects browser requests (no CORS headers), so this
// runs on your server.
async function getPlanetPositions(input) {
  const auth = Buffer.from(
    process.env.ASTRO_USER_ID + ':' + process.env.ASTRO_API_KEY,
  ).toString('base64')

  const res = await fetch(ASTRO_BASE + '/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: 'Timezone offset, e.g. 5.5' },
      },
      required: ['day', 'month', 'year', 'hour', 'min', 'lat', 'lon', 'tzone'],
    },
  },
]

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

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

// Manual tool_result loop: while the model wants the tool, run it, hand
// back the result, and let the model continue.
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 client.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)

Prompt-side guardrail

The tool loop routes the numbers through the API, but the model still decides what to write.

Tell the model, in the system prompt, to use only the fields the tool returned. Tell it to name any field it wants but does not have, rather than filling the gap with a plausible invention:

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

A missing field is a real outcome. A model told to admit the gap reports it. A model left to its own defaults writes a number that looks right and is not.

When to use what

  • MCP server — for agent and IDE contexts (Claude Code, Cursor, Claude Desktop) where the client already speaks MCP.
  • Chat API — hosted end to end, with no tool-calling code required on your side.
  • Roll your own tool calling (this guide) — when you want full control over the prompt and the interpretation step, for example inside your own product's chat feature.

For a longer walkthrough of building with the model server, see building with LLMs.

Caching computed charts across a conversation

The computed values for a fixed birth date, time, and location never change. Once you have called get_planet_positions for a birth-detail tuple, cache the result server-side keyed on that tuple (day/month/year/hour/min/lat/lon/tzone).

A follow-up question in the same conversation, or a later session about the same person, reads the cache instead of calling the API again.

For the fuller caching discussion, see the production checklist.

Related guides

If your inputs come from users rather than a fixed test tuple, get the birth data right before you ground anything on it. See timezones, DST, and historical birth data and when the birth time is unknown.

Browse the full set on the guides index.