Guides
Birth chart API in Python: get a natal chart
A birth chart, also called a natal chart, is a map of the sky at the moment someone was born. It shows where the Sun, Moon and planets were, seen from the birth place.
This guide shows how to get one in Python with the requests package. You turn a place name into coordinates, get the UTC offset, print the Sun, Moon and rising sign, and fetch a chart image. The last step joins it all into one script you can copy.
https://json.astrologyapi.com/v1 with a JSON body. Send your access token in the x-astrologyapi-key header. Plan subscribers can use HTTP Basic auth instead, with the user ID as the username and the API key as the password. We don't send CORS headers, so call the API from your server, not the browser. Prefer Node.js? Read the JavaScript version of this guide.What a birth chart API returns
You send us a birth date, a local birth time, the latitude and longitude, and the UTC offset. We return the sign, degree and house of each planet, the house cusps (the start of each of the 12 houses) and the ascendant.
The ascendant, or rising sign, is the sign rising on the eastern horizon at birth. Western astrology uses the tropical zodiac, measured from the seasons. Vedic astrology uses the sidereal zodiac, measured from the fixed stars. We have endpoints for both.
| Call | Zodiac | What we return |
|---|---|---|
| western_horoscope | Tropical (Western) | Planets, houses, aspects, ascendant and midheaven in one call. Needs house_type and is_asteroids. |
| planets/tropical | Tropical (Western) | A list of planets plus an Ascendant entry. Needs house_type. |
| house_cusps/tropical | Tropical (Western) | The 12 house cusps, plus ascendant, midheaven and vertex. Needs house_type. |
| natal_wheel_chart | Tropical (Western) | A chart_url for a round chart wheel image. Needs house_type, colors, chart_size and image_type. |
| planets | Sidereal (Vedic) | A list of planets with sign, house and nakshatra (lunar mansion), plus an Ascendant entry. |
| horo_chart_image/:chartId | Sidereal (Vedic) | An SVG of a Vedic chart. Use D1 for the birth chart. |
Setup
You need Python 3.8 or later and one package. Then pick your credentials:
- New accounts and wallet users: use your access token. Create it on the credentials screen of your dashboard. We read it from the
x-astrologyapi-keyheader. - Plan subscribers: you can use your user ID and API key instead, sent as HTTP Basic auth.
The two options are compared in Access tokens vs API keys. Put your credentials in environment variables. Never write them into your code.
pip install requests
# New accounts and wallet users: your access token
export ASTROLOGY_ACCESS_TOKEN="your-access-token"
# Plan subscribers can use the user ID and API key instead
# export ASTROLOGY_USER_ID="your-user-id"
# export ASTROLOGY_API_KEY="your-api-key"Check that your token works with one call:
curl -s https://json.astrologyapi.com/v1/geo_details \
-H "x-astrologyapi-key: $ASTROLOGY_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"place":"Mumbai","maxRows":1}'With a user ID and API key, replace the token header with -u "$ASTROLOGY_USER_ID:$ASTROLOGY_API_KEY".
Every step below uses this small helper. It sends JSON, uses the token when it is set and Basic auth when it is not, and stops after 15 seconds:
import os
import requests
BASE_URL = "https://json.astrologyapi.com/v1"
def auth_options():
"""Access token if set, otherwise the user ID and API key."""
token = os.environ.get("ASTROLOGY_ACCESS_TOKEN")
if token:
return {"headers": {"x-astrologyapi-key": token}}
return {"auth": (os.environ["ASTROLOGY_USER_ID"],
os.environ["ASTROLOGY_API_KEY"])} # HTTP Basic auth
def call_api(endpoint, body):
"""POST a JSON body to one endpoint and return the parsed JSON."""
resp = requests.post(
f"{BASE_URL}/{endpoint}",
json=body, # sends Content-Type: application/json
timeout=15,
**auth_options(),
)
resp.raise_for_status()
return resp.json()Step 1: Get coordinates and the UTC offset for the birth place
Users type a city name, but the chart needs numbers. geo_details turns a place name into latitude and longitude. timezone_with_dst then gives the UTC offset at that place on the birth date, with daylight saving time (DST) included.
# Birth: 10 May 1990, 19:55 local time, Mumbai.
geo = call_api("geo_details", {"place": "Mumbai", "maxRows": 1})
match = geo["geonames"][0]
lat = float(match["latitude"])
lon = float(match["longitude"]) # we return longitude as a string
zone = call_api("timezone_with_dst", {
"latitude": lat,
"longitude": lon,
"date": "05-10-1990", # the BIRTH date, as MM-DD-YYYY
})
tzone = float(zone["timezone"]) # hours from UTC, for example 5.5
print(match["place_name"], lat, lon, tzone)geo_details takes place and maxRows (how many matches to return). Sample response, trimmed:
{
"geonames": [
{
"place_name": "Mumbai",
"latitude": 19.07283,
"longitude": "72.88261",
"timezone_id": "Asia/Kolkata",
"country_code": "IN"
}
]
}Look at the types. latitude is a number here, but longitude is a string. The code calls float() on both, so it works either way.
timezone_with_dst takes latitude, longitude and an optional date in MM-DD-YYYY format. Always send the birth date. Sample response:
{
"status": true,
"timezone": 5.5,
"timezone_in_ms": 19800000,
"date": "1992-12-01T00:00:00.000Z"
}Read timezone. It is the offset in hours, such as 5.5 for India. Pass it as tzone in every chart call. For old dates and DST edge cases, read Timezones, DST, and birth data.
Step 2: Call the chart endpoint and print the Sun, Moon and rising sign
Western (tropical) chart
western_horoscope returns the whole Western chart in one call. It needs the eight birth fields, plus house_type (the house system) and is_asteroids.
SIGNS = [
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
]
def sign_of(longitude):
"""Turn a longitude from 0 to 360 into a zodiac sign name."""
return SIGNS[int(longitude // 30) % 12]
birth = {
"day": 10, "month": 5, "year": 1990,
"hour": 19, "min": 55,
"lat": lat, "lon": lon, "tzone": tzone,
}
chart = call_api("western_horoscope", {
**birth,
"house_type": "placidus",
"is_asteroids": False,
})
planets = {p["name"]: p for p in chart["planets"]}
print("Sun: ", planets["Sun"]["sign"])
print("Moon: ", planets["Moon"]["sign"])
print("Rising:", sign_of(chart["ascendant"]))Sample response from our docs, trimmed:
{
"planets": [
{
"name": "Sun",
"full_degree": 275.6427,
"norm_degree": 5.6427,
"speed": 1.019,
"is_retro": "false",
"sign_id": 10,
"sign": "Capricorn",
"house": 2
},
{
"name": "Moon",
"full_degree": 12.271,
"norm_degree": 12.271,
"speed": 13.5085,
"is_retro": "false",
"sign_id": 1,
"sign": "Aries",
"house": 5
}
// ...Mars to Pluto, Node, Chiron, Part of Fortune
],
"houses": [
{ "house": 1, "sign": "Sagittarius", "degree": 240.71431 }
// ...houses 2 to 12
],
"ascendant": 240.71431015862024,
"midheaven": 156.92135925483103
// ...vertex, lilith, aspects
}ascendant is a longitude from 0 to 360. Each sign covers 30 degrees, starting with Aries at 0. So sign_of(240.71) returns Sagittarius. With this sample, the script prints Sun Capricorn, Moon Aries and Rising Sagittarius.
Need less data? planets/tropical returns only the planet list, with the rising sign in its Ascendant entry. house_cusps/tropical returns only the houses and angles.
Vedic (sidereal) chart
For Vedic astrology, call planets with the same birth dictionary. It needs no house_type. The rising sign, called the lagna, is the entry named Ascendant.
vedic = call_api("planets", birth) # no house_type here
by_name = {p["name"]: p for p in vedic}
print("Sun: ", by_name["Sun"]["sign"])
print("Moon: ", by_name["Moon"]["sign"], by_name["Moon"]["nakshatra"])
print("Lagna:", by_name["Ascendant"]["sign"])Sample response, trimmed:
[
{
"id": 0,
"name": "Sun",
"fullDegree": 72.18954079246434,
"normDegree": 12.189540792464342,
"isRetro": "false",
"sign": "Gemini",
"nakshatra": "Ardra",
"house": 9
},
{
"id": 1,
"name": "Moon",
"fullDegree": 11.72351598586304,
"normDegree": 11.72351598586304,
"isRetro": "false",
"sign": "Aries",
"nakshatra": "Ashwini",
"house": 7
},
// ...Mars to Ketu
{
"id": 9,
"name": "Ascendant",
"fullDegree": 207.92868587120444,
"normDegree": 27.928685871204436,
"isRetro": false,
"sign": "Libra",
"nakshatra": "Vishakha",
"house": 1
}
]Note the field names. Western endpoints use full_degree and is_retro. This one uses fullDegree and isRetro. On planets, isRetro is the string "true" or "false", so compare it as a string.
Step 3: Get a chart wheel image
For a Western wheel, call natal_wheel_chart. It takes the birth fields, house_type, and a few style fields. We draw the wheel and return a link to the image.
wheel = call_api("natal_wheel_chart", {
**birth,
"house_type": "placidus",
"planet_icon_color": "#F57C00",
"inner_circle_background": "#FFF8E1",
"sign_icon_color": "red",
"sign_background": "#ffffff",
"chart_size": 500,
"image_type": "png",
})
print(wheel["chart_url"]){
"status": true,
"chart_url": "https://s3.ap-south-1.amazonaws.com/western-chart/65a7ab90-df91-11e9-9ab4-a70005bb269b.svg",
"msg": "Chart created successfully!"
}Show chart_url in an image tag. If you want to serve it from your own storage, download a copy with requests.get.
For a Vedic chart, call horo_chart_image/D1. The chart ID goes in the URL path. D1 is the birth chart, and chartType picks the drawing style, such as north or south.
d1 = call_api("horo_chart_image/D1", {**birth, "chartType": "north"})
with open("chart-d1.svg", "w", encoding="utf-8") as f:
f.write(d1["svg"])The sample response in our docs is {svg:"SVG CODE"}. We return the SVG markup as text in the svg field, not as an image file. Save it as a .svg file or put it straight into your HTML.
Step 4: The complete script
This script chains every step into one function, birth_chart(). It raises a clear error for missing credentials, timeouts, network errors and any response that is not HTTP 200. Save it as birth_chart.py and run python birth_chart.py.
"""birth_chart.py: get a natal chart from AstrologyAPI.
Usage:
pip install requests
export ASTROLOGY_ACCESS_TOKEN="your-access-token"
# or, for plan subscribers:
# export ASTROLOGY_USER_ID="your-user-id"
# export ASTROLOGY_API_KEY="your-api-key"
python birth_chart.py
"""
import os
import sys
import requests
BASE_URL = "https://json.astrologyapi.com/v1"
TIMEOUT_SECONDS = 15
SIGNS = [
"Aries", "Taurus", "Gemini", "Cancer", "Leo", "Virgo",
"Libra", "Scorpio", "Sagittarius", "Capricorn", "Aquarius", "Pisces",
]
WHEEL_STYLE = {
"planet_icon_color": "#F57C00",
"inner_circle_background": "#FFF8E1",
"sign_icon_color": "red",
"sign_background": "#ffffff",
"chart_size": 500,
"image_type": "png",
}
class AstrologyAPIError(Exception):
"""Raised when a call to AstrologyAPI fails."""
def auth_options():
"""Use the access token if set, otherwise the user ID and API key."""
token = os.environ.get("ASTROLOGY_ACCESS_TOKEN")
if token:
return {"headers": {"x-astrologyapi-key": token}}
user_id = os.environ.get("ASTROLOGY_USER_ID")
api_key = os.environ.get("ASTROLOGY_API_KEY")
if user_id and api_key:
return {"auth": (user_id, api_key)} # HTTP Basic auth
raise AstrologyAPIError(
"Set ASTROLOGY_ACCESS_TOKEN, or ASTROLOGY_USER_ID and ASTROLOGY_API_KEY."
)
def call_api(endpoint, body):
"""POST a JSON body to one endpoint. Raise AstrologyAPIError on failure."""
options = auth_options()
try:
resp = requests.post(
f"{BASE_URL}/{endpoint}",
json=body,
timeout=TIMEOUT_SECONDS,
**options,
)
except requests.Timeout as exc:
raise AstrologyAPIError(
f"{endpoint}: no response after {TIMEOUT_SECONDS} seconds"
) from exc
except requests.RequestException as exc:
raise AstrologyAPIError(f"{endpoint}: network error: {exc}") from exc
if resp.status_code != 200:
raise AstrologyAPIError(
f"{endpoint}: HTTP {resp.status_code}: {resp.text[:300]}"
)
try:
return resp.json()
except ValueError as exc:
raise AstrologyAPIError(f"{endpoint}: response was not JSON") from exc
def sign_of(longitude):
"""Turn a longitude from 0 to 360 into a zodiac sign name."""
return SIGNS[int(longitude // 30) % 12]
def find_place(place):
"""Return (place_name, lat, lon) for the best match of a place name."""
geo = call_api("geo_details", {"place": place, "maxRows": 1})
matches = geo.get("geonames") or []
if not matches:
raise AstrologyAPIError(f"No place found for {place!r}")
best = matches[0]
return best["place_name"], float(best["latitude"]), float(best["longitude"])
def utc_offset(lat, lon, day, month, year):
"""Return the UTC offset in hours at this place on the birth date."""
zone = call_api("timezone_with_dst", {
"latitude": lat,
"longitude": lon,
"date": f"{month:02d}-{day:02d}-{year}",
})
return float(zone["timezone"])
def birth_chart(day, month, year, hour, minute, place,
system="western", house_type="placidus"):
"""Return the Sun, Moon and rising sign, plus a chart image.
system="western" uses the tropical zodiac and returns chart_url.
system="vedic" uses the sidereal zodiac and returns chart_svg.
"""
if system not in ("western", "vedic"):
raise ValueError("system must be 'western' or 'vedic'")
place_name, lat, lon = find_place(place)
tzone = utc_offset(lat, lon, day, month, year)
birth = {
"day": day, "month": month, "year": year,
"hour": hour, "min": minute,
"lat": lat, "lon": lon, "tzone": tzone,
}
result = {"system": system, "place": place_name,
"lat": lat, "lon": lon, "tzone": tzone}
if system == "western":
chart = call_api("western_horoscope", {
**birth, "house_type": house_type, "is_asteroids": False,
})
planets = {p["name"]: p for p in chart["planets"]}
result["rising"] = sign_of(chart["ascendant"])
wheel = call_api("natal_wheel_chart", {
**birth, "house_type": house_type, **WHEEL_STYLE,
})
if not wheel.get("chart_url"):
raise AstrologyAPIError(
f"natal_wheel_chart: {wheel.get('msg', 'no chart_url')}"
)
result["chart_url"] = wheel["chart_url"]
else:
planets = {p["name"]: p for p in call_api("planets", birth)}
result["rising"] = planets["Ascendant"]["sign"]
d1 = call_api("horo_chart_image/D1", {**birth, "chartType": "north"})
result["chart_svg"] = d1["svg"]
result["sun"] = planets["Sun"]["sign"]
result["moon"] = planets["Moon"]["sign"]
return result
if __name__ == "__main__":
try:
chart = birth_chart(10, 5, 1990, 19, 55, "Mumbai", system="western")
except AstrologyAPIError as exc:
print(f"Error: {exc}", file=sys.stderr)
sys.exit(1)
print(f"{chart['place']} (UTC{chart['tzone']:+g})")
print(f"Sun: {chart['sun']}")
print(f"Moon: {chart['moon']}")
print(f"Rising: {chart['rising']}")
print(f"Wheel: {chart['chart_url']}")Pass system="vedic" to get the sidereal signs and a D1 chart SVG instead. To use it in a web app, call birth_chart() from your Flask, Django or FastAPI view and return the result as JSON.
A chart for one birth never changes. Cache the result, keyed by the birth fields, so each chart uses credits only once. Retry only when you get a 5xx status. A 4xx status means the request itself is wrong, so fix it before you try again.
Common mistakes
- Using the browser's or server's UTC offset. The offset on the user's device is for today, in their current city. A birth chart needs the offset at the birth place on the birth date. Get it from
timezone_with_dst. - Forgetting
house_type. It is required onwestern_horoscope,planets/tropical,house_cusps/tropicalandnatal_wheel_chart. Sendplacidusif you have no preference. Other options includekochandwhole_sign. - Calling the API from the browser. We don't send CORS headers, so the browser blocks the call. It would also expose your access token or API key. Keep every call in your Python backend.
- Mixing tropical and sidereal results. Don't take the Sun from
planetsand the ascendant fromhouse_cusps/tropical. The two zodiacs are about 24 degrees apart, so the chart will be wrong. Pick one system per chart. The guide on why charts differ explains more.
Go further
- Add aspects, house reports and text readings with the Western birth chart integration guide.
- Build a full Vedic kundli with dashas and divisional charts in the kundli API integration guide.
- Get the rising sign alone with the ascendant calculation guide.
Every new account gets 150 free credits, so you can run this script today. See exact costs in the credits calculator.
Frequently asked questions
Do I need a Python astrology library to get a natal chart?
No. Our API is plain HTTPS with JSON, so the requests package is enough. You send the birth data and we return the calculated positions. You do not need to install or update ephemeris files (tables of planet positions) on your server.
Which endpoint returns a full natal chart in one call?
For a Western chart, call western_horoscope. It returns planets, houses, aspects, the ascendant and the midheaven together. For a Vedic chart, call planets. It returns each planet with its sign, house and nakshatra, plus an Ascendant entry.
Can I call the birth chart API from a browser?
No. We do not send CORS headers, so browsers block direct calls. Your access token or API key would also be visible to every visitor. Call us from your Python backend and send only the results to the browser.
Why do the Western and Vedic results show different signs?
Western endpoints use the tropical zodiac, which is measured from the seasons. Vedic endpoints use the sidereal zodiac, which is measured from the fixed stars. The two are about 24 degrees apart today, so a planet can fall in a different sign in each system.
What if the user does not know their birth time?
The Sun sign is usually safe without a time. The Moon can change sign during the day, and the rising sign changes about every two hours. Do not show a rising sign or houses without a real birth time. Our guide on unknown birth times covers the options.
How many credits does one birth chart use?
Each endpoint uses a set number of credits. Use the credits calculator for exact numbers and the pricing page for plans. Every new account starts with 150 free credits.
For AI agents
- All calls: POST to
https://json.astrologyapi.com/v1/<endpoint>with a JSON body. Auth: headerx-astrologyapi-key: <access token>, or HTTP Basic with user ID and API key for plan subscribers. Server-side only. In Python:requests.post(url, json=body, headers=..., timeout=15). - Place:
geo_detailswithplace,maxRows. Readgeonames[0], andfloat()the latitude and longitude. - Offset:
timezone_with_dstwithlatitude,longitude,date(MM-DD-YYYY birth date). Readtimezoneand send it astzone. - Western:
western_horoscopewith the eight birth fields plushouse_typeandis_asteroids. Rising sign isSIGNS[int(ascendant // 30)]. Wheel image:natal_wheel_chart, readchart_url. - Vedic:
planetswith the eight birth fields. Rising sign is the entry wherename == "Ascendant". Chart image:horo_chart_image/D1, readsvg. - Birth fields:
day,month,year,hour,min(integers) andlat,lon,tzone(floats). Never mix tropical and sidereal results in one chart.