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, the code pattern is identical, just swap the URL.

What you can build with this

  • 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

What you need, 30 seconds

  1. A Roxy API key. Get one on the pricing page.
  2. Two birth dates, times, and cities. We geocode the cities in step 2.

Step 1, call your first endpoint

The compatibility score is one POST with two people. Pick a language and paste.

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 to use: overallScore (0-100), relationshipArchetype, keyAspects[], elementBalance, advice.

Step 2, scaffold the page

Create compatibility.html. The HTML is 50 lines, the script 40.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title>Zodiac Compatibility</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body { font-family: system-ui, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; background: #fafafa; }
    h1 { text-align: center; margin-bottom: 8px; }
    .subtitle { text-align: center; color: #666; margin-bottom: 24px; }
    .people { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 24px; }
    .person { background: white; padding: 16px; border-radius: 12px; }
    .person h3 { margin-bottom: 12px; }
    label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 4px; }
    input, select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 6px; margin-bottom: 10px; }
    button.check { display: block; width: 100%; padding: 14px; background: #7c3aed; color: white; border: none; border-radius: 12px; font-size: 16px; font-weight: 600; cursor: pointer; margin-bottom: 24px; }
    .result { display: none; }
    .ring { text-align: center; margin-bottom: 20px; }
    .ring .n { font-size: 64px; font-weight: 700; color: #7c3aed; }
    .ring .l { font-size: 14px; color: #666; }
    .cats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 20px; }
    .cat { background: white; padding: 12px; border-radius: 8px; }
    .cat .k { font-size: 12px; font-weight: 600; text-transform: uppercase; color: #666; }
    .cat .v { font-size: 20px; font-weight: 600; }
    .insights { background: white; padding: 16px; border-radius: 12px; }
    .insights h3 { margin-bottom: 8px; }
    .insights ul { padding-left: 20px; margin-bottom: 12px; }
    .summary { padding: 12px; background: #f5f0ff; border-radius: 8px; margin-bottom: 16px; }
    @media (max-width: 500px) { .people, .cats { grid-template-columns: 1fr; } }
  </style>
</head>
<body>
  <h1>Zodiac Compatibility</h1>
  <p class="subtitle">Two birth charts in, one score out</p>

  <div class="people">
    <div class="person">
      <h3>Person 1</h3>
      <label>Birth date</label><input type="date" id="p1-date" value="1990-07-15" />
      <label>Birth time</label><input type="time" id="p1-time" value="14:30" />
      <label>City</label>
      <select id="p1-city">
        <option value="40.7128,-74.006,America/New_York">New York</option>
        <option value="34.0522,-118.2437,America/Los_Angeles">Los Angeles</option>
        <option value="51.5074,-0.1278,Europe/London">London</option>
        <option value="19.076,72.8777,Asia/Kolkata">Mumbai</option>
        <option value="35.6762,139.6503,Asia/Tokyo">Tokyo</option>
      </select>
    </div>
    <div class="person">
      <h3>Person 2</h3>
      <label>Birth date</label><input type="date" id="p2-date" value="1992-03-22" />
      <label>Birth time</label><input type="time" id="p2-time" value="09:00" />
      <label>City</label>
      <select id="p2-city">
        <option value="34.0522,-118.2437,America/Los_Angeles">Los Angeles</option>
        <option value="40.7128,-74.006,America/New_York">New York</option>
        <option value="51.5074,-0.1278,Europe/London">London</option>
        <option value="19.076,72.8777,Asia/Kolkata">Mumbai</option>
        <option value="35.6762,139.6503,Asia/Tokyo">Tokyo</option>
      </select>
    </div>
  </div>

  <button class="check" onclick="run()">Check compatibility</button>
  <div id="result" class="result"></div>

  <script>
    const API_KEY = 'YOUR_API_KEY';

    function read(prefix) {
      const [lat, lng, tz] = document.getElementById(prefix + '-city').value.split(',');
      return {
        date: document.getElementById(prefix + '-date').value,
        time: document.getElementById(prefix + '-time').value + ':00',
        latitude: parseFloat(lat),
        longitude: parseFloat(lng),
        timezone: tz,
      };
    }

    async function run() {
      const el = document.getElementById('result');
      el.style.display = 'block';
      el.innerHTML = '<div style="text-align:center;padding:40px;">Comparing birth charts...</div>';

      const res = await fetch('https://roxyapi.com/api/v2/astrology/compatibility-score', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-API-Key': API_KEY },
        body: JSON.stringify({ person1: read('p1'), person2: read('p2') }),
      });
      const data = await res.json();

      const cats = data.elementBalance || {};
      const catEntries = Object.entries(cats);

      el.innerHTML =
        '<div class="ring"><div class="n">' + (data.overallScore ?? '') + '</div><div class="l">out of 100</div></div>' +
        (data.advice ? '<div class="summary">' + data.advice + '</div>' : '') +
        (catEntries.length
          ? '<div class="cats">' + catEntries.map(([k, v]) =>
              '<div class="cat"><div class="k">' + k + '</div><div class="v">' + v + '</div></div>'
            ).join('') + '</div>'
          : '') +
        '<div class="insights">' +
        (data.keyAspects && data.keyAspects.length
          ? '<h3>Key aspects</h3><ul>' + data.keyAspects.map(k =>
              '<li>' + (typeof k === 'string' ? k : (k.description || JSON.stringify(k))) + '</li>'
            ).join('') + '</ul>'
          : '') +
        (data.relationshipArchetype
          ? '<h3>Archetype</h3><p>' + data.relationshipArchetype + '</p>'
          : '') +
        '</div>';
    }
  </script>
</body>
</html>

Replace YOUR_API_KEY. Save. Double-click to open. Click the button.

Timezone is passed as an IANA string ("America/New_York") not a decimal. The server resolves it to the DST-correct offset for the birth date, so a January 1990 New York birth gets EST, a July birth gets EDT. You no longer need to guess whether to send -4 or -5.

Step 3, upgrade to production

Three upgrades that take this from demo to real.

  1. Move the key server-side. Put the fetch behind a Next.js route, a Vercel function, or a Cloudflare Worker. The Next.js integration guide is the drop-in recipe.
  2. Geocode any city. Replace the dropdown with GET /api/v2/location/search?q={query}. Roxy returns a paginated list with latitude, longitude, and IANA timezone. Use cities[0].
  3. Swap compatibility-score for synastry. POST /astrology/synastry returns inter-aspects, strengths, challenges, and a compatibilityScore. Same inputs, richer output. Premium tier.

For matrimonial apps targeting India, swap to POST /vedic-astrology/compatibility for the full 36-point Ashtakoota breakdown with dosha cancellation. See the Vedic astrology guide.

Step 4, deploy

Drop the file into Cloudflare Pages, Vercel, or Netlify. Three-minute deploy. Swap the hardcoded key for an environment variable and a serverless proxy before you share the URL.

Gotchas

  • Do not ship the API key in browser code. The HTML above works on localhost. For anything public, move the fetch to a serverless function.
  • 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 leads to degraded accuracy, not a server error.
  • IANA beats decimal for DST correctness. "America/New_York" resolves to EST or EDT automatically. -5 is always EST.
  • Synastry is /astrology/synastry, not /synastry. The route is nested under /astrology. Easy typo.

What to build next