1. Docs
  2. What To Build
  3. Dating Compatibility App

Build a zodiac dating compatibility app

Ship a two-chart compatibility UI that returns a 0 to 100 score with strengths and challenges. Time to ship: 30 minutes.

Dating and matchmaking apps are the highest-demand pattern in the astrology API market. This tutorial wires the compatibility-score feature that sits on a match card. The underlying call is POST /astrology/compatibility-score. For the full synastry report with inter-aspects, swap one URL.

What you can build

  • Match-card compatibility badges (0 to 100 score)
  • Pro-tier synastry readings with inter-aspect breakdowns
  • Matrimonial apps using Vedic Gun Milan (36-point Ashtakoota)
  • Numerology compatibility side-features ("Life Path 5 meets Life Path 7")
  • AI matchmaker chatbots that read two users and explain the fit

Prerequisites

  1. A Roxy API key from /account.
  2. Two birth dates, times, and cities. You convert each city to lat/lng/timezone in Step 1.

Install

npm install @roxyapi/sdk

Step 1: Location lookup (mandatory)

Every chart call needs latitude, longitude, and timezone (IANA string preferred over decimal). Use GET /location/search to convert a free-text city into those three fields. Cache the result per user.

curl "https://roxyapi.com/api/v2/location/search?q=New+York" \
  -H "X-API-Key: $ROXY_API_KEY"
# returns cities[0] with latitude, longitude, timezone (IANA name)

IANA timezone strings ("America/New_York") DST-resolve against the birth date automatically. A January 1990 New York chart gets EST, a July chart gets EDT. Decimal offsets (-5) also work but never DST-correct.

Step 2: Call the compatibility endpoint

calculateCompatibility takes two people, returns a 0 to 100 overall score with breakdowns. Verified via jq -r '.paths."/astrology/compatibility-score".post.operationId' /tmp/openapi.json.

curl -X POST https://roxyapi.com/api/v2/astrology/compatibility-score \
  -H "X-API-Key: $ROXY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "person1": {"date":"1990-07-15","time":"14:30:00","latitude":40.7128,"longitude":-74.006,"timezone":"America/New_York"},
    "person2": {"date":"1992-03-22","time":"09:00:00","latitude":34.0522,"longitude":-118.2437,"timezone":"America/Los_Angeles"}
  }'

Response fields exposed: overallScore (0-100), archetype ({ label, description }), categories ({ romantic, emotional, intellectual, physical, spiritual } sub-scores), keyAspects[], strengths[], challenges[], elementBalance, summary, interpretation.

Render the result

<roxy-compatibility-card> from @roxyapi/ui renders the score, category breakdown, archetype, summary, strengths, challenges, and key aspects. Pass the unwrapped SDK response, the card draws itself. No hand-built markup, no .map over aspects, no manual score ring. Works in vanilla HTML, React, Vue, Svelte, WordPress, anything.

Option A: server fetch, client render

Fetch with the secret key on your server, then send the response (never the key) to the card in the browser. The mode="astrology" attribute tells the card it is rendering a compatibility-score response.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Zodiac Compatibility</title>
  <script src="https://cdn.jsdelivr.net/npm/@roxyapi/ui@0/dist/cdn/roxy-ui.js" defer></script>
</head>
<body>
  <roxy-compatibility-card id="card" mode="astrology"></roxy-compatibility-card>

  <script type="module">
    // Your backend route holds the secret key and calls the SDK (see below).
    // It returns the unwrapped compatibility-score response as JSON.
    const scoreResponse = await fetch('/api/compatibility', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        person1: { date: '1990-07-15', time: '14:30:00', latitude: 40.71427, longitude: -74.00597, timezone: 'America/New_York' },
        person2: { date: '1992-03-22', time: '09:00:00', latitude: 34.05223, longitude: -118.24368, timezone: 'America/Los_Angeles' },
      }),
    }).then((r) => r.json());

    document.getElementById('card').data = scoreResponse;
  </script>
</body>
</html>

Your /api/compatibility route calls the typed SDK and returns the unwrapped data. The card never sees the key.

// app/api/compatibility/route.ts (Next.js route handler, runs server side)
import { createRoxy } from '@roxyapi/sdk';

const roxy = createRoxy(process.env.ROXY_API_KEY!);

export async function POST(req: Request) {
  const { person1, person2 } = await req.json();
  const { data } = await roxy.astrology.calculateCompatibility({ body: { person1, person2 } });
  return Response.json(data); // unwrapped: pass `data`, never the { data, error } envelope
}

Fetch the response on your server, where the secret key lives. Never put a secret sk_* key in page source: it is harvestable in seconds. For client-side fetching once published, use a publishable key (pk_live_* / pk_test_*, origin-restricted, coming soon) or proxy through a backend (the Next.js guide is the drop-in pattern).

Option B: no-build, server-rendered (inline JSON)

When the page is served from a static host or a cache and there is no client JavaScript to set .data, fetch on your server with the secret key and inline the unwrapped response into the card as a child <script type="application/json" class="roxy-data">. The component reads it on load. No key in the browser, same <roxy-compatibility-card> render. This is Pattern 7 from the UI components page.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Zodiac Compatibility</title>
  <script src="https://cdn.jsdelivr.net/npm/@roxyapi/ui@0/dist/cdn/roxy-ui.js" defer></script>
</head>
<body>
  <roxy-compatibility-card mode="astrology">
    <script type="application/json" class="roxy-data">
      {
        "overallScore": 43,
        "archetype": { "label": "Growth Partners", "description": "This relationship is a catalyst for transformation." },
        "categories": { "romantic": 78, "emotional": 28, "intellectual": 58, "physical": 36, "spiritual": 15 },
        "summary": "Compatibility score: 43/100 (moderate).",
        "keyAspects": [
          { "planet1": "Uranus", "planet2": "Jupiter", "type": "TRINE", "orb": 0.07, "description": "Uranus and Jupiter connect harmoniously." }
        ]
      }
    </script>
  </roxy-compatibility-card>
</body>
</html>

Your server template writes the JSON in. The inlined JSON is the exact shape calculateCompatibility returns, the same object you would assign to .data. Inline the unwrapped response, never the SDK envelope. Setting the JavaScript .data property later always wins over the inlined JSON, so one card tag covers both server-rendered and dynamic pages with no branching.

React

In React projects use @roxyapi/ui-react. The card types mode as a literal-union prop, so a typo fails the build. Import the response type from the SDK rather than declaring a local interface.

'use client';

import { RoxyCompatibilityCard } from '@roxyapi/ui-react';
import type { PostAstrologyCompatibilityScoreResponse } from '@roxyapi/sdk';

export function CompatibilityView({ data }: { data: PostAstrologyCompatibilityScoreResponse }) {
  return <RoxyCompatibilityCard data={data} mode="astrology" />;
}

Full synastry dual-wheel

For the inter-aspect view, swap the endpoint to POST /astrology/synastry (calculateSynastry) and the component to <roxy-synastry-chart>. The synastry response carries compatibilityScore, an interAspects[] table, and analysis with strengths and challenges. The chart renders the inter-aspects table plus the score from this response directly.

// Server side
import { createRoxy } from '@roxyapi/sdk';

const roxy = createRoxy(process.env.ROXY_API_KEY!);

const { data } = await roxy.astrology.calculateSynastry({
  body: {
    person1: { date: '1990-07-15', time: '14:30:00', latitude: 40.71427, longitude: -74.00597, timezone: 'America/New_York' },
    person2: { date: '1992-03-22', time: '09:00:00', latitude: 34.05223, longitude: -118.24368, timezone: 'America/Los_Angeles' },
  },
});
// Send `data` (not the envelope) to the browser, then: synastryChart.data = data;

To draw the full dual-wheel rather than just the inter-aspects table, merge each person planets array from POST /astrology/natal-chart (generateNatalChart) into data.person1.planets and data.person2.planets before assigning. Without planet positions the synastry response has none, so the chart falls back to the inter-aspects table and score. For a single birth wheel on a profile page, render <roxy-natal-chart> with a generateNatalChart response.

Ready-made starter

The full mobile build lives at /starters/astrology-starter-app: React Native + Expo + TypeScript, horoscopes, natal charts, synastry, transits, all wired through @roxyapi/sdk. Clone:

git clone https://github.com/RoxyAPI/astrology-starter-app
cd astrology-starter-app
npm install
echo "EXPO_PUBLIC_ROXYAPI_KEY=your_key" > .env.local
npm start

For the conversational variant ("describe two people, get the score"), the astrology-ai-chatbot starter is the flagship Next.js + Remote MCP reference.

Upgrade to production

  1. Move the key server-side. Browser-embedded secret keys are for prototyping only. Put the fetch behind a Next.js route, Vercel function, or Cloudflare Worker. The Next.js integration guide is the drop-in recipe.
  2. Collect cities with <roxy-location-search>. Drop in the geocoding input from @roxyapi/ui instead of asking users to type coordinates. It emits roxy-location-select with latitude, longitude, and timezone, which feed straight into the compatibility call. Behind the scenes it hits /location/search (cities[0]).
  3. Swap to full synastry. POST /astrology/synastry (calculateSynastry) returns inter-aspects, strengths, challenges, and a compatibilityScore. Same inputs, richer output, premium tier.
  4. Matrimonial India market. Swap to POST /vedic-astrology/compatibility for full 36-point Ashtakoota with dosha cancellation. The vedic guide covers it end-to-end.

Gotchas

  • Do not ship a secret API key in browser code. Use a publishable key (pk_live_*, origin-restricted, coming soon) or proxy through a backend.
  • Time must include seconds. 14:30:00 not 14:30. Most <input type="time"> elements return HH:MM, so append :00 before sending.
  • Both persons need full birth data. date, time, latitude, longitude, timezone. Missing time degrades accuracy, no server error.
  • IANA beats decimal for DST correctness. "America/New_York" resolves to EST or EDT automatically. -5 is always EST.
  • Synastry path is /astrology/synastry, not /synastry. Nested under /astrology. Easy typo.
  • Calculations verified against NASA JPL Horizons. Score breakdowns are deterministic per pair of birth data.

What to build next

  • The tarot app tutorial adds a second reading type for dating apps with the same fetch pattern.
  • The AI chatbot tutorial lets users type birth data in natural language and have an LLM call this endpoint for them.
  • The astrology guide lists every Western endpoint you can swap in.
  • The vedic guide covers Gun Milan for matrimonial builds.