Birth Chart API: Generate and Render Natal Charts Programmatically #
A birth chart (also called a natal chart) is the foundational artifact in astrology. It maps the positions of the Sun, Moon, planets, and other celestial points at the exact moment and location of a person’s birth. Building a natal chart from scratch requires Swiss Ephemeris calculations, house system formulas, aspect geometry, and SVG rendering logic. A birth chart API handles all of this with a single HTTP request.
This tutorial walks through generating natal charts with the Astrologer API, from fetching raw planetary data to rendering professional SVG charts and parsing every piece of the response.
What a Birth Chart API Returns #
A complete birth chart API response includes:
- Planetary positions: Sign, degree, house, retrograde status, speed, and declination for each planet and point (Sun, Moon, Mercury through Pluto, Chiron, Lilith, Lunar Nodes).
- House cusps: The degree of each house cusp in the chosen house system.
- Axes: Ascendant, Descendant, Midheaven (MC), and Imum Coeli (IC).
- Aspects: Angular relationships between planets (conjunction, opposition, trine, square, sextile, and more) with exact orb values.
- Element and quality distribution: Percentage breakdown of Fire/Earth/Air/Water and Cardinal/Fixed/Mutable.
- SVG chart: A ready-to-display wheel chart (from chart endpoints).
Getting Raw Natal Data #
The subject endpoint returns the calculated astrological data for a birth moment without any chart rendering.
Python #
import requests
API_URL = "https://astrologer.p.rapidapi.com/api/v5"
HEADERS = {
"Content-Type": "application/json",
"X-RapidAPI-Key": "YOUR_API_KEY",
"X-RapidAPI-Host": "astrologer.p.rapidapi.com"
}
subject_data = {
"name": "John Doe",
"year": 1990,
"month": 1,
"day": 1,
"hour": 12,
"minute": 30,
"city": "London",
"nation": "GB",
"longitude": -0.1278,
"latitude": 51.5074,
"timezone": "Europe/London"
}
response = requests.post(
f"{API_URL}/subject",
json={"subject": subject_data},
headers=HEADERS
)
data = response.json()
subject = data["subject"]
# Print all planetary positions
planets = ["sun", "moon", "mercury", "venus", "mars",
"jupiter", "saturn", "uranus", "neptune", "pluto"]
for planet_name in planets:
p = subject[planet_name]
retro = " (R)" if p["retrograde"] else ""
print(f"{p['name']:10s} {p['sign']:4s} {p['position']:6.2f} "
f"House: {p['house']:20s}{retro}")
Output:
Sun Cap 10.84 House: Ninth_House
Moon Pis 3.55 House: Eleventh_House
Mercury Cap 25.67 House: Tenth_House (R)
Venus Aqu 6.22 House: Tenth_House (R)
Mars Sag 10.01 House: Seventh_House
Jupiter Can 5.15 House: Third_House (R)
Saturn Cap 15.66 House: Tenth_House
Uranus Cap 5.79 House: Ninth_House
Neptune Cap 12.04 House: Tenth_House
Pluto Sco 17.09 House: Seventh_House
JavaScript #
const API_URL = "https://astrologer.p.rapidapi.com/api/v5";
const HEADERS = {
"Content-Type": "application/json",
"X-RapidAPI-Key": "YOUR_API_KEY",
"X-RapidAPI-Host": "astrologer.p.rapidapi.com"
};
const response = await fetch(`${API_URL}/subject`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
subject: {
name: "John Doe",
year: 1990,
month: 1,
day: 1,
hour: 12,
minute: 30,
city: "London",
nation: "GB",
longitude: -0.1278,
latitude: 51.5074,
timezone: "Europe/London"
}
})
});
const data = await response.json();
const subject = data.subject;
const planets = [
"sun", "moon", "mercury", "venus", "mars",
"jupiter", "saturn", "uranus", "neptune", "pluto"
];
for (const name of planets) {
const p = subject[name];
const retro = p.retrograde ? " (R)" : "";
console.log(
`${p.name.padEnd(10)} ${p.sign.padEnd(4)} ${p.position.toFixed(2).padStart(6)} House: ${p.house}${retro}`
);
}
Understanding the Planetary Data Structure #
Each planet object in the response contains rich data. Here is the full structure for one planet:
{
"name": "Sun",
"quality": "Cardinal",
"element": "Earth",
"sign": "Cap",
"sign_num": 9,
"position": 10.84,
"abs_pos": 280.84,
"emoji": "♑️",
"point_type": "AstrologicalPoint",
"house": "Ninth_House",
"retrograde": false,
"speed": 1.02,
"declination": -23.00,
"magnitude": null
}
Key fields explained:
position: Degree within the sign (0-30). The Sun at 10.84 Cap means 10 degrees and 50 minutes of Capricorn.abs_pos: Absolute ecliptic longitude (0-360). Useful for computing aspects manually.sign_num: Zero-indexed sign number (Aries = 0, Taurus = 1, …, Pisces = 11).house: The house this planet occupies in the chosen house system.retrograde: Whether the planet appears to move backward from Earth’s perspective.speed: Daily motion in degrees. Negative values indicate retrograde motion.declination: Angular distance north or south of the celestial equator.
Generating SVG Charts #
The natal chart endpoint returns a professional SVG wheel chart alongside all the computed data.
Python #
def get_natal_chart_svg(subject_data, theme="classic"):
"""Fetch an SVG natal chart with computed data."""
response = requests.post(
f"{API_URL}/chart/birth-chart",
json={
"subject": subject_data,
"theme": theme
},
headers=HEADERS
)
return response.json()
chart_response = get_natal_chart_svg({
"name": "John Doe",
"year": 1990,
"month": 1,
"day": 1,
"hour": 12,
"minute": 30,
"city": "London",
"nation": "GB",
"longitude": -0.1278,
"latitude": 51.5074,
"timezone": "Europe/London"
})
# Save the SVG
svg_markup = chart_response["chart"]
with open("birth_chart.svg", "w") as f:
f.write(svg_markup)
# Access the underlying data
chart_data = chart_response["chart_data"]
print(f"Chart type: {chart_data['chart_type']}")
print(f"House system: {chart_data['subject']['houses_system_name']}")
print(f"Zodiac: {chart_data['subject']['zodiac_type']}")
print(f"Aspects found: {len(chart_data['aspects'])}")
JavaScript (Rendering in a Web Page) #
async function renderBirthChart(containerId, subjectData, theme = "classic") {
const response = await fetch(
`${API_URL}/chart/birth-chart`,
{
method: "POST",
headers: HEADERS,
body: JSON.stringify({
subject: subjectData,
theme: theme
})
}
);
const result = await response.json();
// Insert SVG directly into the DOM
const container = document.getElementById(containerId);
container.innerHTML = result.chart;
// Make the SVG responsive
const svg = container.querySelector("svg");
if (svg) {
svg.setAttribute("width", "100%");
svg.setAttribute("height", "auto");
svg.style.maxWidth = "600px";
}
return result.chart_data;
}
// Usage
const chartData = await renderBirthChart("chart-container", {
name: "John Doe",
year: 1990,
month: 1,
day: 1,
hour: 12,
minute: 30,
city: "London",
nation: "GB",
longitude: -0.1278,
latitude: 51.5074,
timezone: "Europe/London"
});
Working With Aspects #
The chart data natal endpoint returns a detailed aspects array. Each aspect includes the two planets involved, the aspect type, the exact orb, and the theoretical angle.
Python: Parsing Aspects #
def get_chart_data(subject_data):
"""Fetch full chart data including aspects and distributions."""
response = requests.post(
f"{API_URL}/chart-data/birth-chart",
json={
"subject": subject_data,
"distribution_method": "weighted"
},
headers=HEADERS
)
return response.json()
chart = get_chart_data({
"name": "John Doe",
"year": 1990,
"month": 1,
"day": 1,
"hour": 12,
"minute": 30,
"city": "London",
"nation": "GB",
"longitude": -0.1278,
"latitude": 51.5074,
"timezone": "Europe/London"
})
aspects = chart["chart_data"]["aspects"]
# Group aspects by type
from collections import Counter
aspect_counts = Counter(a["aspect"] for a in aspects)
print("Aspect distribution:")
for aspect_type, count in aspect_counts.most_common():
print(f" {aspect_type}: {count}")
# Find tight aspects (orb < 2 degrees)
tight_aspects = [a for a in aspects if a["orbit"] < 2.0]
print(f"\nTight aspects (orb < 2 degrees): {len(tight_aspects)}")
for a in tight_aspects:
print(f" {a['p1_name']} {a['aspect']} {a['p2_name']} (orb: {a['orbit']:.2f})")
JavaScript: Filtering Aspects #
const chart = await fetch(`${API_URL}/chart-data/birth-chart`, {
method: "POST",
headers: HEADERS,
body: JSON.stringify({
subject: {
name: "John Doe",
year: 1990, month: 1, day: 1,
hour: 12, minute: 30,
city: "London", nation: "GB",
longitude: -0.1278, latitude: 51.5074,
timezone: "Europe/London"
},
distribution_method: "weighted"
})
}).then(r => r.json());
const aspects = chart.chart_data.aspects;
// Find all aspects involving the Sun
const sunAspects = aspects.filter(
a => a.p1_name === "Sun" || a.p2_name === "Sun"
);
console.log(`Sun aspects (${sunAspects.length}):`);
sunAspects.forEach(a => {
const other = a.p1_name === "Sun" ? a.p2_name : a.p1_name;
console.log(` Sun ${a.aspect} ${other} (orb: ${a.orbit.toFixed(2)})`);
});
Element and Quality Distribution #
The chart data response includes element and quality distributions, showing the balance of the four elements and three qualities in the chart.
elements = chart["chart_data"]["elements_distribution"]
qualities = chart["chart_data"]["qualities_distribution"]
print("Elements:")
for element, pct in elements.items():
bar = "#" * int(pct)
print(f" {element:6s}: {pct:5.1f}% {bar}")
print("\nQualities:")
for quality, pct in qualities.items():
bar = "#" * int(pct)
print(f" {quality:9s}: {pct:5.1f}% {bar}")
Example output:
Elements:
fire : 12.5% ############
earth : 50.0% ##################################################
air : 12.5% ############
water : 25.0% #########################
Qualities:
cardinal : 62.5% ##############################################################
fixed : 25.0% #########################
mutable : 12.5% ############
Switching House Systems #
The API supports nine house systems. Change the system by setting houses_system_identifier in the subject object.
# Compare Placidus vs Whole Sign houses
for system, code in [("Placidus", "P"), ("Whole Sign", "W"), ("Koch", "K")]:
subject = {
"name": "John Doe",
"year": 1990, "month": 1, "day": 1,
"hour": 12, "minute": 30,
"city": "London", "nation": "GB",
"longitude": -0.1278, "latitude": 51.5074,
"timezone": "Europe/London",
"houses_system_identifier": code
}
resp = requests.post(
f"{API_URL}/subject",
json={"subject": subject},
headers=HEADERS
).json()
sun_house = resp["subject"]["sun"]["house"]
moon_house = resp["subject"]["moon"]["house"]
print(f"{system:12s} Sun: {sun_house:20s} Moon: {moon_house}")
The house system affects which house each planet falls in, which changes the interpretation significantly. Offering users a choice of house system is a common feature in astrology apps.
Sidereal and Tropical Calculations #
By default the API uses the Tropical zodiac. For Vedic (Jyotish) astrology, set zodiac_type to "Sidereal" and provide a sidereal_mode.
vedic_subject = {
"name": "John Doe",
"year": 1990, "month": 1, "day": 1,
"hour": 12, "minute": 30,
"city": "London", "nation": "GB",
"longitude": -0.1278, "latitude": 51.5074,
"timezone": "Europe/London",
"zodiac_type": "Sidereal",
"sidereal_mode": "LAHIRI",
"houses_system_identifier": "W"
}
vedic_resp = requests.post(
f"{API_URL}/subject",
json={"subject": vedic_subject},
headers=HEADERS
).json()
v = vedic_resp["subject"]
print(f"Sidereal Sun: {v['sun']['sign']} at {v['sun']['position']:.2f}")
print(f"Ayanamsa: {v['ayanamsa_value']:.4f}")
Chart Customization Options #
The chart endpoints accept several rendering options:
| Parameter | Description |
|---|---|
theme |
Visual theme for the chart (e.g., "classic") |
language |
Language for labels |
split_chart |
Boolean to split the chart into segments |
transparent_background |
Boolean for transparent SVG background |
show_degree_indicators |
Boolean to show degree tick marks |
show_aspect_icons |
Boolean to display aspect glyphs |
custom_title |
Override the default chart title |
See the natal chart endpoint docs for the full list of options.
Building a Birth Chart Component #
Here is a complete example of a reusable function that fetches and renders a birth chart with error handling.
class BirthChartRenderer {
constructor(apiKey) {
this.apiUrl = "https://astrologer.p.rapidapi.com/api/v5";
this.headers = {
"Content-Type": "application/json",
"X-RapidAPI-Key": apiKey,
"X-RapidAPI-Host": "astrologer.p.rapidapi.com"
};
}
async render(container, birthData, options = {}) {
const { theme = "classic", houseSystem = "P" } = options;
try {
const response = await fetch(`${this.apiUrl}/chart/birth-chart`, {
method: "POST",
headers: this.headers,
body: JSON.stringify({
subject: { ...birthData, houses_system_identifier: houseSystem },
theme
})
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
const result = await response.json();
// Render SVG
container.innerHTML = result.chart;
const svg = container.querySelector("svg");
if (svg) {
svg.setAttribute("width", "100%");
svg.setAttribute("height", "auto");
}
return {
subject: result.chart_data.subject,
aspects: result.chart_data.aspects,
elements: result.chart_data.elements_distribution,
qualities: result.chart_data.qualities_distribution
};
} catch (error) {
container.textContent = `Failed to load chart: ${error.message}`;
throw error;
}
}
}
// Usage
const renderer = new BirthChartRenderer("YOUR_API_KEY");
const chartData = await renderer.render(
document.getElementById("chart"),
{
name: "John Doe",
year: 1990, month: 1, day: 1,
hour: 12, minute: 30,
city: "London", nation: "GB",
longitude: -0.1278, latitude: 51.5074,
timezone: "Europe/London"
},
{ theme: "classic", houseSystem: "P" }
);
Next Steps #
- Get your API key on RapidAPI
- See the natal chart SVG example for a full response sample
- Read about the chart data endpoint for data-only responses
- Learn to build horoscopes with transit data
- Follow the complete app building tutorial