Moon Phase API: Build Lunar Tracking Features #
Moon phase data powers a surprisingly wide range of applications. Astrology apps display the current lunar phase. Gardening apps time planting by the moon cycle. Fishing apps predict activity based on lunar illumination. Photography apps help users plan moonrise shots. Wellness apps track cycles tied to lunar rhythms. The Astrologer API provides a dedicated moon phase endpoint that returns comprehensive lunar data for any date, time, and location, including phase name, illumination percentage, upcoming phase dates, eclipse predictions, and sun position.
This tutorial walks through the moon phase endpoint in detail and shows how to build practical lunar features with it.
The Moon Phase Endpoint #
The moon phase endpoint uses a simpler request format than the other Astrologer API endpoints. Instead of a nested subject object, it takes date/time and coordinates directly at the top level. This is because moon phase data does not require a person’s name or city – just when and where.
Important: this endpoint uses strict validation. Sending unexpected fields like name, city, or subject will result in a 422 error. Keep the request body clean.
See the moon phase endpoint docs for the full schema and all response fields.
Fetching Moon Phase Data #
Before you begin, get your API key on RapidAPI.
Python #
import requests
url = "https://astrologer.p.rapidapi.com/api/v5/moon-phase"
headers = {
"Content-Type": "application/json",
"X-RapidAPI-Key": "YOUR_API_KEY",
"X-RapidAPI-Host": "astrologer.p.rapidapi.com"
}
payload = {
"year": 2026,
"month": 4,
"day": 21,
"hour": 20,
"minute": 0,
"latitude": 41.9028,
"longitude": 12.4964,
"timezone": "Europe/Rome"
}
response = requests.post(url, json=payload, headers=headers)
data = response.json()
moon = data["moon_phase_overview"]["moon"]
sun = data["moon_phase_overview"]["sun"]
print(f"Phase: {moon['phase_name']}")
print(f"Illumination: {moon['illumination']}")
print(f"Stage: {moon['stage']}")
print(f"Age: {moon['age_days']} days")
print(f"Lunar cycle: {moon['lunar_cycle']}")
print(f"Moon emoji: {moon['emoji']}")
print(f"Sun sign: {moon['zodiac']['sun_sign']}")
print(f"Moon sign: {moon['zodiac']['moon_sign']}")
print(f"Sunrise: {sun['sunrise_timestamp']}")
print(f"Sunset: {sun['sunset_timestamp']}")
print(f"Day length: {sun['day_length']}")
JavaScript #
const url = "https://astrologer.p.rapidapi.com/api/v5/moon-phase";
const payload = {
year: 2026,
month: 4,
day: 21,
hour: 20,
minute: 0,
latitude: 41.9028,
longitude: 12.4964,
timezone: "Europe/Rome",
};
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-RapidAPI-Key": "YOUR_API_KEY",
"X-RapidAPI-Host": "astrologer.p.rapidapi.com",
},
body: JSON.stringify(payload),
});
const data = await response.json();
const moon = data.moon_phase_overview.moon;
const sun = data.moon_phase_overview.sun;
console.log(`Phase: ${moon.phase_name}`);
console.log(`Illumination: ${moon.illumination}`);
console.log(`Stage: ${moon.stage}`);
console.log(`Age: ${moon.age_days} days`);
console.log(`Emoji: ${moon.emoji}`);
console.log(`Sunrise: ${sun.sunrise_timestamp}`);
console.log(`Sunset: ${sun.sunset_timestamp}`);
Understanding the Response #
The response contains a moon_phase_overview object with three main sections: moon, sun, and location.
Moon Data #
The phase field is a float from 0.0 to 1.0 representing the position within the lunar cycle: 0.0 is the New Moon, approximately 0.5 is the Full Moon, and 1.0 is the next New Moon. The phase_name field translates this into one of eight named phases: "New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous", "Full Moon", "Waning Gibbous", "Last Quarter", "Waning Crescent".
The illumination field gives you a human-readable percentage like "32%", while detailed.illumination_details provides the raw number (32.0), the visible fraction (0.324), and the phase angle in degrees.
The stage field is either "waxing" (moon growing brighter) or "waning" (moon dimming). The age_days field tells you how many days have passed since the last New Moon.
Upcoming Phases #
The detailed.upcoming_phases object is particularly valuable for building lunar calendars. It contains new_moon, first_quarter, full_moon, and last_quarter, each with last and next sub-objects:
{
"upcoming_phases": {
"full_moon": {
"last": {
"timestamp": 1775692800,
"datestamp": "Sun, 12 Apr 2026 18:00:00 +0000",
"days_ago": 9
},
"next": {
"timestamp": 1778284800,
"datestamp": "Tue, 12 May 2026 12:00:00 +0000",
"days_ahead": 21
}
}
}
}
Eclipse Predictions #
The response also includes the next lunar and solar eclipse from the queried date:
{
"next_lunar_eclipse": {
"timestamp": 1788652800,
"datestamp": "Fri, 11 Sep 2026 12:00:00 +0000",
"type": "Total Lunar Eclipse",
"visibility_regions": null
}
}
The type field can be values like "Total Lunar Eclipse", "Partial Lunar Eclipse", "Annular Solar Eclipse", or "Partial Solar Eclipse".
Sun Data #
The sun section includes sunrise, sunset, solar_noon, day_length, and the sun’s current position (altitude, azimuth, distance). The timestamp fields (sunrise, sunset) are Unix timestamps, while sunrise_timestamp and sunset_timestamp are formatted local time strings like "06:23".
Building a Moon Phase Widget #
Here is a complete example that builds a moon phase widget with current data and upcoming phases:
Python #
import requests
from datetime import datetime, timezone
def get_moon_widget_data(lat, lng, tz_name):
"""Get formatted moon phase data for a widget display."""
now = datetime.now(timezone.utc)
url = "https://astrologer.p.rapidapi.com/api/v5/moon-phase"
headers = {
"Content-Type": "application/json",
"X-RapidAPI-Key": "YOUR_API_KEY",
"X-RapidAPI-Host": "astrologer.p.rapidapi.com"
}
payload = {
"year": now.year,
"month": now.month,
"day": now.day,
"hour": now.hour,
"minute": now.minute,
"latitude": lat,
"longitude": lng,
"timezone": tz_name,
"location_precision": 2
}
response = requests.post(url, json=payload, headers=headers)
data = response.json()["moon_phase_overview"]
moon = data["moon"]
sun = data["sun"]
upcoming = moon["detailed"]["upcoming_phases"]
widget = {
"current": {
"phase_name": moon["phase_name"],
"emoji": moon["emoji"],
"illumination": moon["illumination"],
"stage": moon["stage"],
"age_days": moon["age_days"],
"moon_sign": moon["zodiac"]["moon_sign"],
"sun_sign": moon["zodiac"]["sun_sign"],
},
"sun_times": {
"sunrise": sun["sunrise_timestamp"],
"sunset": sun["sunset_timestamp"],
"day_length": sun["day_length"],
"solar_noon": sun["solar_noon"],
},
"upcoming": {
"next_new_moon": upcoming["new_moon"]["next"]["datestamp"],
"days_to_new_moon": upcoming["new_moon"]["next"]["days_ahead"],
"next_full_moon": upcoming["full_moon"]["next"]["datestamp"],
"days_to_full_moon": upcoming["full_moon"]["next"]["days_ahead"],
"next_first_quarter": upcoming["first_quarter"]["next"]["datestamp"],
"next_last_quarter": upcoming["last_quarter"]["next"]["datestamp"],
},
"eclipses": {
"next_lunar": moon["next_lunar_eclipse"]["datestamp"],
"lunar_type": moon["next_lunar_eclipse"]["type"],
"next_solar": sun["next_solar_eclipse"]["datestamp"],
"solar_type": sun["next_solar_eclipse"]["type"],
}
}
return widget
# Example: Moon data for London
widget = get_moon_widget_data(51.5074, -0.1278, "Europe/London")
print(f"{widget['current']['emoji']} {widget['current']['phase_name']}")
print(f"Illumination: {widget['current']['illumination']}")
print(f"Moon in {widget['current']['moon_sign']}")
print(f"Moon age: {widget['current']['age_days']} days")
print(f"\nSunrise: {widget['sun_times']['sunrise']}")
print(f"Sunset: {widget['sun_times']['sunset']}")
print(f"Day length: {widget['sun_times']['day_length']}")
print(f"\nNext Full Moon: {widget['upcoming']['next_full_moon']}"
f" ({widget['upcoming']['days_to_full_moon']} days)")
print(f"Next New Moon: {widget['upcoming']['next_new_moon']}"
f" ({widget['upcoming']['days_to_new_moon']} days)")
print(f"\nNext lunar eclipse: {widget['eclipses']['next_lunar']}"
f" ({widget['eclipses']['lunar_type']})")
JavaScript #
async function getMoonWidgetData(lat, lng, tzName) {
const now = new Date();
const url = "https://astrologer.p.rapidapi.com/api/v5/moon-phase";
const payload = {
year: now.getFullYear(),
month: now.getMonth() + 1,
day: now.getDate(),
hour: now.getHours(),
minute: now.getMinutes(),
latitude: lat,
longitude: lng,
timezone: tzName,
location_precision: 2,
};
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-RapidAPI-Key": "YOUR_API_KEY",
"X-RapidAPI-Host": "astrologer.p.rapidapi.com",
},
body: JSON.stringify(payload),
});
const data = await response.json();
const moon = data.moon_phase_overview.moon;
const sun = data.moon_phase_overview.sun;
const upcoming = moon.detailed.upcoming_phases;
return {
current: {
phaseName: moon.phase_name,
emoji: moon.emoji,
illumination: moon.illumination,
stage: moon.stage,
ageDays: moon.age_days,
moonSign: moon.zodiac.moon_sign,
},
sunTimes: {
sunrise: sun.sunrise_timestamp,
sunset: sun.sunset_timestamp,
dayLength: sun.day_length,
},
upcoming: {
nextFullMoon: upcoming.full_moon.next.datestamp,
daysToFullMoon: upcoming.full_moon.next.days_ahead,
nextNewMoon: upcoming.new_moon.next.datestamp,
daysToNewMoon: upcoming.new_moon.next.days_ahead,
},
};
}
// Usage
const widget = await getMoonWidgetData(51.5074, -0.1278, "Europe/London");
console.log(`${widget.current.emoji} ${widget.current.phaseName}`);
console.log(`Illumination: ${widget.current.illumination}`);
console.log(`Next Full Moon in ${widget.upcoming.daysToFullMoon} days`);
Building a Lunar Calendar #
To build a monthly lunar calendar, make one request per day of the month and collect the phase data:
import requests
def build_lunar_calendar(year, month, lat, lng, tz_name):
"""Build a lunar calendar for an entire month."""
import calendar
url = "https://astrologer.p.rapidapi.com/api/v5/moon-phase"
headers = {
"Content-Type": "application/json",
"X-RapidAPI-Key": "YOUR_API_KEY",
"X-RapidAPI-Host": "astrologer.p.rapidapi.com"
}
days_in_month = calendar.monthrange(year, month)[1]
calendar_data = []
for day in range(1, days_in_month + 1):
payload = {
"year": year,
"month": month,
"day": day,
"hour": 12,
"minute": 0,
"latitude": lat,
"longitude": lng,
"timezone": tz_name
}
response = requests.post(url, json=payload, headers=headers)
moon = response.json()["moon_phase_overview"]["moon"]
calendar_data.append({
"day": day,
"phase_name": moon["phase_name"],
"emoji": moon["emoji"],
"illumination": moon["illumination"],
"moon_sign": moon["zodiac"]["moon_sign"],
"stage": moon["stage"],
})
return calendar_data
# Build April 2026 lunar calendar for New York
cal = build_lunar_calendar(2026, 4, 40.7128, -74.006, "America/New_York")
for day_data in cal:
print(f" Apr {day_data['day']:2d}: {day_data['emoji']} "
f"{day_data['phase_name']:<20s} {day_data['illumination']:>4s} "
f"Moon in {day_data['moon_sign']}")
For production use, consider making these requests in parallel or caching the results since lunar data for a specific date and location does not change.
Gardening and Farming Use Cases #
Lunar gardening follows a long tradition of timing agricultural activities to the moon’s phase and zodiac sign. The API provides both pieces of data.
General lunar gardening rules that you can implement:
- Waxing moon (New to Full): Plant above-ground crops, especially leafy greens during the first quarter and fruiting crops during the second quarter.
- Full Moon: Harvest root crops, transplant, prune.
- Waning moon (Full to New): Plant root crops, do maintenance, weed.
- New Moon: Rest period, plan and prepare soil.
The moon_sign field adds another layer. Water signs (Cancer, Scorpio, Pisces) and Earth signs (Taurus, Virgo, Capricorn) are considered fertile and favorable for planting. Fire signs (Aries, Leo, Sagittarius) are considered barren and better for cultivation and weeding. Air signs (Gemini, Libra, Aquarius) are neutral.
FERTILE_SIGNS = {"Can", "Sco", "Pis", "Tau", "Vir", "Cap"}
BARREN_SIGNS = {"Ari", "Leo", "Sag"}
def get_gardening_advice(moon_data):
"""Generate simple gardening advice from moon phase data."""
moon = moon_data["moon_phase_overview"]["moon"]
moon_sign = moon["zodiac"]["moon_sign"]
stage = moon["stage"]
phase_name = moon["phase_name"]
is_fertile = moon_sign in FERTILE_SIGNS
is_waxing = stage == "waxing"
if phase_name == "New Moon":
activity = "Rest day. Plan your garden and prepare soil."
elif is_waxing and is_fertile:
activity = "Excellent day for planting above-ground crops."
elif is_waxing and not is_fertile:
activity = "Good day for cultivating, weeding, and pest control."
elif not is_waxing and is_fertile:
activity = "Good day for planting root crops and transplanting."
elif phase_name == "Full Moon":
activity = "Harvest root crops. Good for transplanting."
else:
activity = "Focus on maintenance: pruning, composting, weeding."
return {
"date_phase": f"{moon['emoji']} {phase_name}",
"moon_in": moon_sign,
"fertile": is_fertile,
"advice": activity
}
Location-Aware Features #
Moon phase data is location-dependent. The moon’s illumination percentage is the same globally, but moonrise and moonset times, as well as sun times, vary by location. The location_precision parameter controls how many decimal places are used for the latitude and longitude in the response, which is useful for privacy-conscious applications where you do not want to store or display exact coordinates.
# Low precision (city-level) for privacy
payload = {
"year": 2026, "month": 4, "day": 21,
"hour": 20, "minute": 0,
"latitude": 51.5074, "longitude": -0.1278,
"timezone": "Europe/London",
"location_precision": 0 # Response shows lat "52", lng "0"
}
# High precision for exact moonrise calculations
payload_precise = {
"year": 2026, "month": 4, "day": 21,
"hour": 20, "minute": 0,
"latitude": 51.5074, "longitude": -0.1278,
"timezone": "Europe/London",
"location_precision": 4 # Response shows lat "51.5074", lng "-0.1278"
}
Caching Strategy #
Moon phase data changes slowly enough that aggressive caching makes sense:
- Current phase display: Cache for 1 hour. The phase name does not change that often, and illumination changes by roughly 1% per hour.
- Upcoming phase dates: Cache for 24 hours. The predicted dates of upcoming full and new moons do not change.
- Eclipse predictions: Cache for days or weeks. Eclipse dates are fixed astronomical events.
- Sunrise/sunset times: Cache for 24 hours per location. These change by only a minute or two per day.
For more about the Astrologer API’s full capabilities, visit the Astrologer API landing page. Get your API key on RapidAPI to start building lunar features.