Zodiac Compatibility API: Building a Love Calculator
Compatibility is one of the most popular features in any astrology app — and with a zodiac compatibility API, you can power a full love calculator in an afternoon. This guide covers the astrology behind synastry, the GitStrology compatibility endpoint, the SDK, element-based matching, and a complete React UI you can adapt for your own product.
What Is Synastry?
In astrology, synastry is the practice of comparing two natal charts to evaluate relationship compatibility. Rather than looking at sun signs alone, synastry computes the angular relationships — called aspects — between planets in one person’s chart and planets in the other person’s chart. For example, if Person A’s Venus (love, attraction) forms a harmonious trine (120-degree angle) with Person B’s Mars (passion, drive), that cross-aspect suggests strong romantic chemistry.
A serious compatibility analysis weighs dozens of these cross-aspects: Sun-Moon connections for emotional rapport, Venus-Mars for physical attraction, Mercury-Mercury for communication, and Saturn contacts for longevity and commitment. The GitStrology compatibility API performs this calculation for you and returns structured scores, so you do not need to implement ephemeris math or aspect interpretation yourself.
To follow along, you will need an API key. New accounts start on the free tier with 100 credits per month. Each compatibility call costs 5 credits, so you can run 20 compatibility reports for free. Get started with the quickstart guide if you have not already.
Calling the Compatibility Endpoint
The /v1/compatibility endpoint accepts birth data for two people: date of birth, time of birth, latitude, longitude, and timezone. Birth time is optional but strongly recommended — without it, the Moon, rising sign, and house placements cannot be calculated accurately, which weakens the analysis.
curl -X POST https://api.gitstrology.dev/v1/compatibility \
-H "X-API-Key: gs_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"person1": {
"date": "1990-03-21",
"time": "14:30",
"latitude": 40.7128,
"longitude": -74.0060,
"timezone": "America/New_York"
},
"person2": {
"date": "1988-07-15",
"time": "09:00",
"latitude": 34.0522,
"longitude": -118.2437,
"timezone": "America/Los_Angeles"
}
}'The response returns an overall compatibility score and a categorical breakdown, plus the key cross-aspects that drove the result:
{
"data": {
"overallScore": 78,
"verdict": "strong",
"person1": { "sunSign": "aries", "moonSign": "leo", "risingSign": "cancer" },
"person2": { "sunSign": "cancer", "moonSign": "scorpio", "risingSign": "libra" },
"breakdown": {
"emotional": 82,
"communication": 71,
"physical": 85,
"intellectual": 69,
"longevity": 76
},
"keyAspects": [
{
"planet1": "venus",
"planet2": "mars",
"aspect": "trine",
"orb": 1.2,
"interpretation": "Strong romantic and physical chemistry."
},
{
"planet1": "sun",
"planet2": "moon",
"aspect": "opposition",
"orb": 3.5,
"interpretation": "Complementary but tension-driven dynamic."
}
],
"elementBalance": {
"fire": 2,
"earth": 1,
"air": 1,
"water": 2
}
},
"credits": { "used": 5, "remaining": 95 }
}The overallScore is a 0–100 aggregate. The breakdown object splits compatibility into five dimensions: emotional rapport, communication style, physical attraction, intellectual alignment, and relationship longevity. The keyAspects array surfaces the most influential cross-chart planetary angles, each with a human-readable interpretation you can display directly to users.
For the complete field reference, see the compatibility docs.
Using the SDK
The TypeScript SDK makes compatibility calls trivial. Install it, initialize a client, and call gs.compatibility():
npm install gitstrology
// lib/gitstrology.ts
import { Gitstrology } from "gitstrology";
export const gs = new Gitstrology({
apiKey: process.env.GITSTROLOGY_API_KEY,
});import { gs } from "@/lib/gitstrology";
const result = await gs.compatibility({
person1: {
date: "1990-03-21",
time: "14:30",
latitude: 40.7128,
longitude: -74.0060,
timezone: "America/New_York",
},
person2: {
date: "1988-07-15",
time: "09:00",
latitude: 34.0522,
longitude: -118.2437,
timezone: "America/Los_Angeles",
},
});
console.log(result.data.overallScore); // 78
console.log(result.data.verdict); // "strong"
console.log(result.data.breakdown); // { emotional: 82, ... }
console.log(result.credits.remaining); // 95The SDK is fully typed — pass the wrong shape and TypeScript will catch it at compile time, before you ever deploy. This eliminates an entire class of runtime errors that raw HTTP calls invite.
Element Compatibility: Fire, Earth, Air, Water
Beyond full chart synastry, element-based matching is a fast, lightweight way to give users a quick compatibility snapshot. The twelve signs are grouped into four elements, and each pair of elements has a natural affinity or friction:
- Fire (Aries, Leo, Sagittarius) — pairs well with Air; clashes with Water.
- Earth (Taurus, Virgo, Capricorn) — pairs well with Water; clashes with Fire.
- Air (Gemini, Libra, Aquarius) — pairs well with Fire; clashes with Earth.
- Water (Cancer, Scorpio, Pisces) — pairs well with Earth; clashes with Air.
Elements of the same triplicity (e.g., two Fire signs) usually get along well but may lack balance. The full compatibility endpoint accounts for this automatically via the elementBalance field in the response, but you can also compute a quick element score client-side:
const ELEMENTS: Record<string, string> = {
aries: "fire", leo: "fire", sagittarius: "fire",
taurus: "earth", virgo: "earth", capricorn: "earth",
gemini: "air", libra: "air", aquarius: "air",
cancer: "water", scorpio: "water", pisces: "water",
};
const COMPAT: Record<string, Record<string, number>> = {
fire: { fire: 80, earth: 40, air: 90, water: 35 },
earth: { fire: 40, earth: 80, air: 35, water: 90 },
air: { fire: 90, earth: 35, air: 80, water: 40 },
water: { fire: 35, earth: 90, air: 40, water: 80 },
};
function elementScore(sign1: string, sign2: string): number {
const e1 = ELEMENTS[sign1.toLowerCase()];
const e2 = ELEMENTS[sign2.toLowerCase()];
if (!e1 || !e2) throw new Error("Invalid sign");
return COMPAT[e1][e2];
}
console.log(elementScore("aries", "gemini")); // 90 (fire + air)
console.log(elementScore("aries", "cancer")); // 35 (fire + water)Use this for a fast, zero-credit preview before the user enters full birth data. Then upgrade them to the full synastry report once they provide birth times and locations.
Building the Love Calculator UI
Here is a complete Next.js server component that collects two people’s birth data and renders a compatibility report. The form POSTs to a Route Handler, which calls the SDK and returns the result:
// app/compatibility/page.tsx
import Link from "next/link";
export default function CompatibilityPage() {
return (
<div className="love-calculator">
<h1>Love Calculator</h1>
<p>Enter both partners’ birth details to see your compatibility.</p>
<form action="/api/compatibility" method="POST">
<fieldset>
<legend>Person 1</legend>
<input name="date1" type="date" required />
<input name="time1" type="time" placeholder="Birth time (optional)" />
<input name="lat1" type="number" step="0.0001" placeholder="Latitude" required />
<input name="lon1" type="number" step="0.0001" placeholder="Longitude" required />
<input name="tz1" type="text" placeholder="America/New_York" required />
</fieldset>
<fieldset>
<legend>Person 2</legend>
<input name="date2" type="date" required />
<input name="time2" type="time" placeholder="Birth time (optional)" />
<input name="lat2" type="number" step="0.0001" placeholder="Latitude" required />
<input name="lon2" type="number" step="0.0001" placeholder="Longitude" required />
<input name="tz2" type="text" placeholder="America/Los_Angeles" required />
</fieldset>
<button type="submit">Calculate Compatibility</button>
</fieldset>
</form>
</div>
);
}// app/api/compatibility/route.ts
import { gs } from "@/lib/gitstrology";
import { NextResponse } from "next/server";
export async function POST(req: Request) {
const form = await req.formData();
const person1 = {
date: String(form.get("date1")),
time: String(form.get("time1") || ""),
latitude: Number(form.get("lat1")),
longitude: Number(form.get("lon1")),
timezone: String(form.get("tz1")),
};
const person2 = {
date: String(form.get("date2")),
time: String(form.get("time2") || ""),
latitude: Number(form.get("lat2")),
longitude: Number(form.get("lon2")),
timezone: String(form.get("tz2")),
};
// Basic validation
for (const person of [person1, person2]) {
if (isNaN(person.latitude) || person.latitude < -90 || person.latitude > 90) {
return NextResponse.json(
{ error: "Invalid latitude. Must be between -90 and 90." },
{ status: 400 }
);
}
if (isNaN(person.longitude) || person.longitude < -180 || person.longitude > 180) {
return NextResponse.json(
{ error: "Invalid longitude. Must be between -180 and 180." },
{ status: 400 }
);
}
}
try {
const result = await gs.compatibility({ person1, person2 });
return NextResponse.json(result.data);
} catch (error: any) {
const status = error.status || 500;
return NextResponse.json(
{ error: error.message || "Compatibility calculation failed." },
{ status }
);
}
}Once the client receives the result, render a visual report with a score gauge, category bars, and the key aspect interpretations. Here is a React component for displaying the breakdown:
type CompatibilityData = {
overallScore: number;
verdict: string;
breakdown: Record<string, number>;
keyAspects: Array<{
planet1: string;
planet2: string;
aspect: string;
interpretation: string;
}>;
};
function ScoreGauge({ score }: { score: number }) {
const color = score >= 75 ? "#22c55e" : score >= 50 ? "#eab308" : "#ef4444";
return (
<div className="gauge" style={{ color }}>
<div className="score">{score}</div>
<div className="label">/ 100</div>
</div>
);
}
function CategoryBars({ breakdown }: { breakdown: Record<string, number> }) {
return (
<div className="categories">
{Object.entries(breakdown).map(([category, score]) => (
<div key={category} className="category">
<span className="cat-label">{category}</span>
<div className="bar-track">
<div className="bar-fill" style={{ width: `${score}%` }} />
</div>
<span className="cat-score">{score}</span>
</div>
))}
</div>
);
}
export function CompatibilityReport({ data }: { data: CompatibilityData }) {
return (
<div className="report">
<ScoreGauge score={data.overallScore} />
<p className="verdict">{data.verdict}</p>
<h3>Compatibility Breakdown</h3>
<CategoryBars breakdown={data.breakdown} />
<h3>Key Aspects</h3>
<ul className="aspects">
{data.keyAspects.map((a, i) => (
<li key={i}>
<strong>{a.planet1} {a.aspect} {a.planet2}</strong>
<p>{a.interpretation}</p>
</li>
))}
</ul>
</div>
);
}Caching and Credit Strategy
Compatibility results are deterministic for a given pair of birth charts, so caching is the single most effective cost control. Compute a stable cache key from both people’s birth data and store the result in Redis or your database. The first user who queries a given pair pays 5 credits; every subsequent query for the same pair is free. Over time, as your user base explores popular sign combinations, your effective cost per query drops dramatically.
import { createHash } from "crypto";
function compatibilityCacheKey(person1: object, person2: object): string {
// Normalize so order doesn't matter
const normalized = JSON.stringify([person1, person2].sort());
return "compat:" + createHash("sha256").update(normalized).digest("hex").slice(0, 16);
}
async function cachedCompatibility(
person1: object,
person2: object,
redis: { get: (k: string) => Promise<string | null>; set: (k: string, v: string) => Promise<void> }
) {
const key = compatibilityCacheKey(person1, person2);
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const result = await gs.compatibility({ person1, person2 });
await redis.set(key, JSON.stringify(result.data));
return result.data;
}On the free tier, 100 credits give you 20 unique compatibility reports. With even modest caching, that covers far more than 20 user queries, because popular sign combinations hit the cache repeatedly. When you are ready to scale, the Pro plan provides 10,000 credits for $19/month — enough for 2,000 unique compatibility calculations before caching multiplies your effective capacity further.
Understanding Aspects and Orbs
The keyAspects array in the response is where the real interpretive value lives. Each aspect describes the angular relationship between a planet in one chart and a planet in the other. The major aspects you will encounter are the conjunction (0 degrees, blending energies), the opposition (180 degrees, tension and complementarity), the trine (120 degrees, harmony and flow), the square (90 degrees, friction and growth), and the sextile (60 degrees, opportunity).
The orb field measures how exact the aspect is — a smaller orb means a stronger, more pronounced effect. An aspect with an orb under 2 degrees is considered tight and influential; one with an orb above 5 degrees is weaker and more subtle. When surfacing aspects to users, lead with the tightest, highest-impact connections and use the provided interpretation text directly — it is written to be user-facing and avoids jargon.
Error Handling and Best Practices
- Always validate birth data before calling the API. Invalid dates, out-of-range coordinates, and malformed times waste credits and produce errors.
- Birth time is optional but recommended. Without it, Moon sign and house placements will be estimated, reducing accuracy.
- Each compatibility call costs 5 credits. Cache results keyed by the two birth-data signatures so repeat queries do not consume credits.
- Handle
402(credit exhaustion) gracefully — show a friendly message and link to upgrade. - Run all API calls server-side. Never expose your API key in client components.
Key Takeaways
- Synastry compares two natal charts via cross-aspects between planets, producing a richer analysis than sun-sign matching alone.
- The
/v1/compatibilityendpoint accepts birth data for two people and returns an overall score, five-category breakdown, key aspects, and element balance. - Each compatibility call costs 5 credits. The free tier covers 20 calls per month; the Pro tier offers 10,000 credits for $19/month.
- Use element-based matching (fire/earth/air/water) for a fast, zero-credit preview before the full synastry report.
- The SDK (
gs.compatibility()) provides full type safety and handles auth and parsing automatically. - Cache results to avoid redundant credit usage, and always validate user input before calling the API.
- Render the breakdown as visual score bars and surface key aspect interpretations for a compelling love calculator UX.
Ready to build your love calculator? Get your API key from the dashboard, follow the quickstart guide, and read the full compatibility endpoint reference for every response field.