1. Docs
  2. What To Build
  3. Complete Birth Profile

Build a complete birth profile for an AI agent

Turn one birth input into every core chart, Western astrology, Vedic, Human Design, and numerology, assembled into a single structured profile your AI agent reads before it answers anything about a person. One key, one fan-out, cache forever. Time to ship: 45 minutes.

An AI agent that talks about someone's chart needs the chart in front of it first. Ask an LLM for a natal chart from memory and it hallucinates degrees, houses, and dasha dates. The fix is to compute the real profile once from verified data and feed it to the model as grounded context.

This guide builds that profile: the location lookup, the four chart domains under one key, the Vedic sub-charts, and the one move most builders miss, stripping the editorial interpretations so the agent reads core numbers, not prose. Numbers are what the model reasons over; the interpretation is what your product writes in its own voice.

What you can build

  • The context block an AI astrology chatbot reads before every reply (grounding layer, stops hallucinated charts)
  • A per-user "profile memory" row for an AI companion, computed once at signup
  • A practitioner one-sheet: a client's full cross-domain chart on a single screen
  • A RAG document per person for a spiritual-insight product
  • Multi-domain dashboards (natal wheel + kundli + numerology card from one payload)

Prerequisites

  1. A Roxy API key from /account.
  2. The person's birth date, time, full name, and city (you geocode the city in Step 1).
  3. A backend you control (Next.js route, Vercel function, Cloudflare Worker, Bun server). The client never sees the secret key.
  4. A persistent store (Postgres, SQLite, Redis, even a JSON column) to cache the assembled profile. Birth data is immutable, so the profile is computed once and cached forever.

Install

npm install @roxyapi/sdk

The worked code below is TypeScript, the reference implementation. Every call has an exact equivalent in the Python, PHP, C#, and Go SDKs. The next section maps the method shape per language.

The shape: one input, four domains, one profile

Every call below takes the same birth data. The lead chart is the natal chart (Western); Vedic is the second domain, then Human Design and numerology, with the Vedic sub-charts deepening it. One key covers all of them, no per-domain fees.

CallDomainEndpointoperationIdReturns
1LocationGET /location/searchsearchCitieslatitude, longitude, timezone
2WesternPOST /astrology/natal-chartgenerateNatalChartplanets, houses, aspects, ascendant, midheaven
3VedicPOST /vedic-astrology/birth-chartgenerateBirthChartrashi chart, planets with nakshatra and pada, yogas
4Human DesignPOST /human-design/bodygraphgenerateBodygraphtype, strategy, authority, profile, centers, channels, gates
5NumerologyPOST /numerology/chartgenerateNumerologyChartlife path, expression, soul urge, personality, birth day, maturity
6Vedic depthdasha, navamsa, yogas, doshas, ashtakavarga, shadbala, KPsee Step 3timing, D9, strengths, dosha flags

Rule 0: location first

Every chart endpoint needs latitude, longitude, and timezone. Never ask a user to type coordinates. Resolve the city once, then pass the result into every chart call.

Call it from any SDK

The same operations exist in all five SDKs. TypeScript, Python, PHP, and Go name each method after the operationId from the shape table above (camelCase in TypeScript and PHP, snake_case in Python, PascalCase in Go); C# is path-fluent and mirrors the URL. Here is the canonical call, the natal chart, in each:

import { createRoxy } from '@roxyapi/sdk';
const roxy = createRoxy(process.env.ROXY_API_KEY!);

const { data: natal } = await roxy.astrology.generateNatalChart({
  body: { date: '1990-07-15', time: '14:30:00', latitude: 40.7128, longitude: -74.006, timezone: 'America/New_York' },
});

The other three charts follow the identical pattern: generateBirthChart, generateBodygraph, and generateNumerologyChart (path-fluent roxy.VedicAstrology.BirthChart, roxy.HumanDesign.Bodygraph, roxy.Numerology.Chart in C#); numerology takes { fullName, year, month, day } instead of the location body. The full per-method list, with request and response types in every language, ships in each SDK's bundled AGENTS.md and in the Agent Task Cheatsheet, both generated from the same spec so they never drift.

Step 1: Location lookup (run once per person)

Convert the free-text city to coordinates and a timezone with the Location API, then reuse the result in every chart call. The raw contract is one authenticated GET; the SDK wraps the same request.

curl -s "https://roxyapi.com/api/v2/location/search?q=New%20York&limit=1" \
  -H "X-API-Key: $ROXY_API_KEY"
# cities[0] => { latitude: 40.7128, longitude: -74.006, timezone: "America/New_York", utcOffset: -5, ... }

Save { latitude, longitude, timezone }. The IANA timezone string resolves server-side to the DST-correct offset for the birth date, so a July 1990 New York birth gets EDT automatically.

Step 2: The four core charts (one birth input, cache forever)

The natal chart, the Vedic kundli, the Human Design bodygraph, and the numerology chart are the foundation. All four are deterministic from immutable birth data, so call them once per person and cache the result. Numerology takes { year, month, day } integers plus the birth-certificate fullName; the chart endpoints take the location from Step 1. Human Design only requires date, time, and timezone, but passing the coordinates sharpens its planetary precision, so reuse the same birth object.

const birth = {
  date: '1990-07-15',
  time: '14:30:00',
  latitude: city.latitude,
  longitude: city.longitude,
  timezone: city.timezone,
};

const [natal, kundli, bodygraph, numerology] = await Promise.all([
  roxy.astrology.generateNatalChart({ body: birth }),
  roxy.vedicAstrology.generateBirthChart({ body: birth }),
  roxy.humanDesign.generateBodygraph({ body: birth }),
  roxy.numerology.generateNumerologyChart({
    body: { fullName: 'Emma Charlotte Wright', year: 1990, month: 7, day: 15 },
  }),
]);

Verified operationIds: generateNatalChart, generateBirthChart, generateBodygraph, generateNumerologyChart.

Prefer raw HTTP? Every POST chart is the same shape, an X-API-Key header and a JSON birth body:

curl -s -X POST "https://roxyapi.com/api/v2/astrology/natal-chart" \
  -H "X-API-Key: $ROXY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"date":"1990-07-15","time":"14:30:00","latitude":40.7128,"longitude":-74.006,"timezone":"America/New_York"}'

Swap the path for /vedic-astrology/birth-chart or /human-design/bodygraph with the same body; /numerology/chart takes {"fullName":"Emma Charlotte Wright","year":1990,"month":7,"day":15}.

Response anchors you will read later:

  • Natal (natal.data): ascendant.{ sign, degree }, midheaven.{ sign, degree }, planets[].{ name, sign, degree, house, isRetrograde }, houses[].{ number, sign, degree }, aspects[].{ planet1, planet2, type, orb }.
  • Kundli (kundli.data): meta.{Graha}.{ rashi, longitude, nakshatra: { name, pada, lord }, house, awastha, isRetrograde } for Sun through Ketu plus Lagna, twelve sign-keyed house entries, and yogas[].
  • Human Design (bodygraph.data): type, strategy, authority, profile, plus centers, channels, gates, and incarnationCross.
  • Numerology (numerology.data): coreNumbers.{ lifePath, expression, soulUrge, personality, birthDay, maturity }, each { number, calculation, type, hasKarmicDebt }, plus additionalInsights, luckyAssociations, summary.

Never ship a secret sk_ key to a browser. Run every call from a backend you control. For widget or no-code embeds, mint a publishable pk_ key locked to your origins instead.

Step 3: Add Vedic depth

For a full profile, layer the Vedic sub-charts on the same birth body. Each is one call, all under the same key.

const [dasha, navamsa, yogas, manglik, kalsarpa, sadhesati, ashtakavarga, shadbala, kp] =
  await Promise.all([
    roxy.vedicAstrology.getCurrentDasha({ body: birth }),
    roxy.vedicAstrology.generateNavamsa({ body: birth }),
    roxy.vedicAstrology.detectYogas({ body: birth }),
    roxy.vedicAstrology.checkManglikDosha({ body: birth }),
    roxy.vedicAstrology.checkKalsarpaDosha({ body: birth }),
    roxy.vedicAstrology.checkSadhesati({ body: birth }),
    roxy.vedicAstrology.calculateAshtakavarga({ body: birth }),
    roxy.vedicAstrology.calculateShadbala({ body: birth }),
    roxy.vedicAstrology.generateKpChart({ body: birth }),
  ]);

Verified operationIds: getCurrentDasha, generateNavamsa, detectYogas, checkManglikDosha, checkKalsarpaDosha, checkSadhesati, calculateAshtakavarga, calculateShadbala, generateKpChart. For the full 120-year timeline instead of just the running period, swap getCurrentDasha for getMajorDashas.

What each adds: getCurrentDasha returns the running mahadasha, antardasha, and pratyantardasha lords (the timing layer). generateNavamsa returns the D9 chart. detectYogas returns yogas[].{ name, present, quality }. Each dosha returns { present } plus details when present. calculateShadbala returns per-planet strength in rupas; calculateAshtakavarga returns bindu totals per sign.

Step 4: Assemble one profile in a single server route

Fan out every call behind one route and return a single payload. Two-thirds of these are cache-forever; only timing-sensitive reads (none here, since dasha boundaries are fixed from birth) would need a TTL.

// app/api/profile/route.ts (Next.js App Router)
import { NextResponse } from 'next/server';
import { createRoxy } from '@roxyapi/sdk';

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

export async function GET(req: Request) {
  const userId = new URL(req.url).searchParams.get('userId')!;

  const cached = await cache.get(`profile:${userId}`);
  if (cached) return NextResponse.json(cached);

  const u = await db.users.findById(userId); // your DB: birth fields + saved location
  const birth = {
    date: u.birthDate, time: u.birthTime,
    latitude: u.latitude, longitude: u.longitude, timezone: u.timezone,
  };

  const [natal, kundli, bodygraph, numerology, dasha, navamsa, yogas, manglik, kalsarpa, sadhesati] =
    await Promise.all([
      roxy.astrology.generateNatalChart({ body: birth }),
      roxy.vedicAstrology.generateBirthChart({ body: birth }),
      roxy.humanDesign.generateBodygraph({ body: birth }),
      roxy.numerology.generateNumerologyChart({
        body: { fullName: u.fullName, year: u.birthYear, month: u.birthMonth, day: u.birthDay },
      }),
      roxy.vedicAstrology.getCurrentDasha({ body: birth }),
      roxy.vedicAstrology.generateNavamsa({ body: birth }),
      roxy.vedicAstrology.detectYogas({ body: birth }),
      roxy.vedicAstrology.checkManglikDosha({ body: birth }),
      roxy.vedicAstrology.checkKalsarpaDosha({ body: birth }),
      roxy.vedicAstrology.checkSadhesati({ body: birth }),
    ]);

  const profile = {
    natal: natal.data, kundli: kundli.data, humanDesign: bodygraph.data, numerology: numerology.data,
    dasha: dasha.data, navamsa: navamsa.data, yogas: yogas.data,
    doshas: { manglik: manglik.data, kalsarpa: kalsarpa.data, sadhesati: sadhesati.data },
  };

  await cache.set(`profile:${userId}`, profile); // immutable, forever
  return NextResponse.json(profile);
}

Step 5: Strip to core numbers for the agent

The full responses carry editorial interpretation text in every domain. An LLM does not need that prose, it needs the structured numbers to reason over, and your product supplies the voice. Reducing the payload to bare values cuts tokens, removes a second opinion the model might parrot, and gives you a clean context block.

function toAgentContext(p: Profile) {
  const cn = p.numerology.coreNumbers;
  return {
    numerology: {
      lifePath: cn.lifePath.number,
      expression: cn.expression.number,
      soulUrge: cn.soulUrge.number,
      personality: cn.personality.number,
      birthDay: cn.birthDay.number,
      maturity: cn.maturity.number,
    },
    western: {
      ascendant: `${p.natal.ascendant.sign} ${p.natal.ascendant.degree.toFixed(2)}`,
      midheaven: `${p.natal.midheaven.sign} ${p.natal.midheaven.degree.toFixed(2)}`,
      planets: p.natal.planets.map((pl) => ({
        body: pl.name, sign: pl.sign, degree: Number(pl.degree.toFixed(2)),
        house: pl.house, retrograde: pl.isRetrograde,
      })),
    },
    humanDesign: {
      type: p.humanDesign.type,
      strategy: p.humanDesign.strategy,
      authority: p.humanDesign.authority,
      profile: p.humanDesign.profile,
    },
    vedic: {
      lagna: p.kundli.meta.Lagna.rashi,
      moonNakshatra: `${p.kundli.meta.Moon.nakshatra.name} pada ${p.kundli.meta.Moon.nakshatra.pada}`,
      planets: Object.values(p.kundli.meta).map((g) => ({
        graha: g.graha, rashi: g.rashi,
        nakshatra: g.nakshatra.name, pada: g.nakshatra.pada, house: g.house,
      })),
      currentDasha: {
        maha: p.dasha.mahadasha.planet,
        antar: p.dasha.antardasha.planet,
        pratyantar: p.dasha.pratyantardasha.planet,
      },
      yogasPresent: p.yogas.yogas.filter((y) => y.present).map((y) => y.name),
      doshas: {
        manglik: p.doshas.manglik.present,
        kalsarpa: p.doshas.kalsarpa.present,
        sadhesati: p.doshas.sadhesati.present,
      },
    },
  };
}

That object is small, fully numeric, and stable. It is the per-person memory row, computed once.

Step 6: Feed it to your LLM as grounded context

Inject the stripped profile into the system prompt. The model now answers from real, verified positions instead of inventing them, and writes in your product's voice.

const context = toAgentContext(profile);

const system = `You are the in-app astrologer for Acme Stars. Warm, specific, never vague.
Answer ONLY from the verified birth profile below. Never invent positions, degrees, or dates; if a question needs data not present here, say so.
Reply in the user's language. Keep domain terms (planet, nakshatra, gate) in their original form. Never mention APIs, tools, or that this profile was precomputed; speak as if you simply know it. End with one actionable insight.

BIRTH PROFILE (verified, core numbers only):
${JSON.stringify(context, null, 2)}`;

// Pass `system` to your model (OpenAI, Anthropic, Gemini, or any provider).

This is the static-context pattern: precompute the profile once, ground every reply on it. For the live tool-calling variant, where the model fetches charts mid-conversation, the open-source astrology-ai-chatbot starter ships a production system prompt (location-first procedure, ambiguous-date disambiguation, multilingual replies, never reveal the tools) worth adapting.

For a chatbot that should also fetch fresh data mid-conversation (today's transits, a compatibility score with another person), keep this profile as the base context and add the per-domain Remote MCP servers so the agent can call verified tools live on top of it.

Optional layers and what is left out

The four charts plus the Vedic depth above are the complete static profile, everything deterministic from one birth input. More layers are available when a product needs them, all on the same key and the same birth body:

  • More Western depth. POST /astrology/asteroids, POST /astrology/arabic-lots, and POST /astrology/fixed-stars add natal points and bodies the core chart does not return.
  • More Vedic depth. POST /vedic-astrology/divisional-chart for any varga beyond D9 (D10 career, D7 children, and so on), POST /vedic-astrology/upagraha for the subtle calculated points, and POST /vedic-astrology/dasha/sub/{mahadasha} for sub-period drill-down.
  • A second numerology system. POST /numerology/chaldean returns the Chaldean (Cheiro) compound numbers alongside the Pythagorean chart, a distinct system some products show side by side.
  • Lifestyle layer. GET /crystals/birthstone/{month} and GET /crystals/zodiac/{sign} map the birth month and sun sign to stones.

Deliberately left out of the static profile:

  • The dynamic layer. Transits, progressions, solar and lunar returns, profections, and the forecast timeline change with the calendar, so they are computed live, not cached into the profile. Add them at request time over Remote MCP on top of the cached profile.
  • Non-birth domains. Tarot, I Ching, dreams, and angel numbers are not derived from birth data (a tarot draw is random, an angel number is a lookup), so they are not part of a birth profile.
  • Biorhythm is birth-date driven but cyclical (it changes every day), so treat it as a daily metric, not a static profile field.

Gotchas

  • Numerology takes integers, not a date string. { year, month, day }, not 1990-07-15. The fullName is the birth-certificate name, used for the letter-based numbers (expression, soul urge, personality).
  • The Vedic kundli is keyed by sign and by graha. generateBirthChart returns a twelve sign-keyed house map plus a meta object keyed by planet. Read meta.{Graha} for a clean per-planet view; do not expect a flat planets[] array like the Western chart.
  • Human Design needs only date, time, and timezone. Coordinates are optional (they refine planetary precision), so it is the one chart that still works when the birth city is unknown. Read type, strategy, authority, and profile from the top level of the response.
  • Strip interpretations for the model, keep them for the UI. The full responses include editorial text in 8 languages. Send the model numbers; render the prose (or your own copy) to the user.
  • Cache the profile forever. Birth data is immutable, so every value here is too. Invalidate only if the user corrects a birth-data typo.
  • One key, every domain. All the calls above bill at a flat 1 request each, REST and MCP identical. No per-domain fees, no batch endpoints.
  • Calculations are verified against NASA JPL Horizons. The same Roxy Ephemeris powers the Western, Vedic, Human Design, and KP layers, so every domain agrees on the same sky.

Frequently asked questions

How many API calls does a full profile take?

One location lookup plus one call per chart. The four core charts (Western, Vedic, Human Design, numerology) are the minimum; the Vedic sub-charts (dasha, navamsa, yogas, doshas, ashtakavarga, shadbala, KP) add one call each. All run in parallel and bill at a flat 1 request each.

Why strip the interpretations before sending to an LLM?

The model reasons over the structured numbers, not the prose. Sending only core numbers cuts token cost, prevents the model from parroting a generic interpretation, and lets your product write the reading in its own voice. Render the editorial text to the user separately if you want it.

Can I cache the assembled profile?

Yes, forever. Every value derives from immutable birth data: planetary positions, nakshatras, dasha boundaries, and numerology numbers never change. Compute once at signup, store the result, and invalidate only if the user fixes a birth-data typo.

Do I need a separate subscription for Western, Vedic, Human Design, and numerology?

No. One API key covers every domain in the catalog. The whole profile is built from a single subscription with flat per-request pricing and no per-domain fees.

What endpoints make up a complete birth profile?

The deterministic, birth-derived ones: astrology/natal-chart, vedic-astrology/birth-chart (plus dasha, navamsa, yogas, doshas, ashtakavarga, shadbala, and kp/chart), human-design/bodygraph, and numerology/chart. Time-varying data (transits, returns, forecast) and non-birth domains (tarot, I Ching, dreams, angel numbers) are not part of the static profile.

What to build next