# 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](/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
```bash
npm install @roxyapi/sdk
```

### Python
```bash
pip install roxy-sdk
```

### PHP
```bash
composer require roxyapi/sdk
```

### C#
```bash
dotnet add package RoxyApi.Sdk
```

### Go
```bash
go get github.com/RoxyAPI/sdk-go
```

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.

| Call | Domain | Endpoint | operationId | Returns |
|---|---|---|---|---|
| 1 | Location | `GET /location/search` | `searchCities` | latitude, longitude, timezone |
| 2 | Western | `POST /astrology/natal-chart` | `generateNatalChart` | planets, houses, aspects, ascendant, midheaven |
| 3 | Vedic | `POST /vedic-astrology/birth-chart` | `generateBirthChart` | rashi chart, planets with nakshatra and pada, yogas |
| 4 | Human Design | `POST /human-design/bodygraph` | `generateBodygraph` | type, strategy, authority, profile, centers, channels, gates |
| 5 | Numerology | `POST /numerology/chart` | `generateNumerologyChart` | life path, expression, soul urge, personality, birth day, maturity |
| 6 | Vedic depth | dasha, navamsa, yogas, doshas, ashtakavarga, shadbala, KP | see Step 3 | timing, 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:


### TypeScript
```typescript
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' },
});
```

### Python
```python
from roxy_sdk import create_roxy
roxy = create_roxy("YOUR_API_KEY")

natal = roxy.astrology.generate_natal_chart(
    date="1990-07-15", time="14:30:00",
    latitude=40.7128, longitude=-74.006, timezone="America/New_York")
```

### PHP
```php
use function RoxyAPI\Sdk\createRoxy;
$roxy = createRoxy(getenv('ROXY_API_KEY'));

$natal = $roxy->astrology->generateNatalChart(
    date: '1990-07-15', time: '14:30:00',
    latitude: 40.7128, longitude: -74.006, timezone: 'America/New_York');
```

### C#
```csharp
using RoxyApi;
using Microsoft.Kiota.Abstractions;
var roxy = new RoxyClient(Environment.GetEnvironmentVariable("ROXY_API_KEY")!);

var natal = await roxy.Astrology.NatalChart.PostAsync(new() {
    Date = new Date(1990, 7, 15), Time = "14:30:00",
    Latitude = 40.7128, Longitude = -74.006,
    Timezone = new() { String = "America/New_York" } });
```

### Go
```go
import (
    "context"
    "time"
    roxyapi "github.com/RoxyAPI/sdk-go"
)

roxy, _ := roxyapi.NewRoxy("YOUR_API_KEY")

var tz roxyapi.NatalChartRequest_Timezone
_ = tz.FromNatalChartRequestTimezone1("America/New_York") // IANA string; FromNatalChartRequestTimezone0(-5) for a numeric offset

natal, _ := roxy.Astrology.GenerateNatalChart(context.Background(), nil, roxyapi.NatalChartRequest{
    Date: roxyapi.Date(1990, time.July, 15), Time: "14:30:00",
    Latitude: 40.7128, Longitude: -74.006, Timezone: tz,
})
```

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](/docs/agent-tasks), 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
```bash
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, ... }
```

### fetch
```javascript
const res = await fetch(
  'https://roxyapi.com/api/v2/location/search?q=New York&limit=1',
  { headers: { 'X-API-Key': process.env.ROXY_API_KEY } },
);
const { cities } = await res.json();
const city = cities[0]; // { latitude, longitude, timezone, utcOffset, ... }
```

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

const { data: loc } = await roxy.location.searchCities({ query: { q: 'New York' } });
const city = loc.cities[0];
```

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.

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

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

**Warning: 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.

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

```typescript
// 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.

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

```typescript
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](https://github.com/RoxyAPI/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](/docs/mcp) 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](/docs/guides/forecast) change with the calendar, so they are computed live, not cached into the profile. Add them at request time over [Remote MCP](/docs/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

- The [AI astrology chatbot tutorial](/docs/tutorials/ai-chatbot) wires this profile into a full conversational app over Remote MCP.
- The [AI companion with memory tutorial](/docs/tutorials/ai-companion-with-memory) adds vector recall of past readings on top of this same profile.
- The [Western astrology](/docs/guides/astrology), [Vedic astrology](/docs/guides/vedic-astrology), [Human Design](/docs/guides/human-design), and [numerology](/docs/guides/numerology) guides cover the rest of the endpoints in each domain.
- The [MCP setup](/docs/mcp) connects your agent to live verified tools so it can fetch fresh data on top of the cached profile.
