<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nick</title>
    <description>The latest articles on DEV Community by Nick (@prowler64).</description>
    <link>https://dev.to/prowler64</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3953358%2Ffcd39765-f70d-4e4d-a755-ccdd847299af.jpg</url>
      <title>DEV Community: Nick</title>
      <link>https://dev.to/prowler64</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/prowler64"/>
    <language>en</language>
    <item>
      <title>I Built an AI Astrology Companion With Memory. Here Is the $1M ARR Napkin Math</title>
      <dc:creator>Nick</dc:creator>
      <pubDate>Sat, 01 Aug 2026 11:15:11 +0000</pubDate>
      <link>https://dev.to/prowler64/i-built-an-ai-astrology-companion-with-memory-here-is-the-1m-arr-napkin-math-26g9</link>
      <guid>https://dev.to/prowler64/i-built-an-ai-astrology-companion-with-memory-here-is-the-1m-arr-napkin-math-26g9</guid>
      <description>&lt;p&gt;Right, so. Picture me at 3am, eleven browser tabs deep, cross-checking a planetary longitude against three different websites that all disagreed with each other by a couple of arcminutes, eating cereal out of a mug because every actual bowl I own was in the sink.&lt;/p&gt;

&lt;p&gt;That was my "astrology app" in month two. Not the app. Just the part where I try to work out where Saturn was.&lt;/p&gt;

&lt;p&gt;I want to be really clear about how bad this was, because I think a lot of devs walk into this exact wall. I was not building a product. I was doing unpaid astronomy homework. And the worst part is you cannot even tell when you get it wrong. A birth chart that is subtly broken looks completely fine. It renders. It has planets in it. Your test users go "ooh, so accurate". Meanwhile the one actual astrologer who signs up takes about nine seconds to go "your ascendant is a whole sign off, mate" and never comes back.&lt;/p&gt;

&lt;p&gt;It is like shooting blind in a dark room and everyone in the room politely telling you that you are hitting the target.&lt;/p&gt;

&lt;p&gt;So here is the writeup I wish I had found on day one: the stack, the memory layer, the grounding rule that stops the LLM inventing charts, and the load math that decides whether this thing is a business or an API bill with a UI on top.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not compute ephemeris yourself. Ground the LLM on a verified insight API and let the model do voice, not math.&lt;/li&gt;
&lt;li&gt;Stack: Next.js App Router, Supabase (Postgres, auth, RLS, pgvector), Vercel AI SDK, and the typed &lt;code&gt;@roxyapi/sdk&lt;/code&gt; for the calculations.&lt;/li&gt;
&lt;li&gt;Memory is yours, calculation is stateless. Store the natal chart once. It is immutable. This one decision is the entire cost model.&lt;/li&gt;
&lt;li&gt;Users switch domains mid-conversation (transits, then tarot, then human design, then I Ching, then numerology). One key covers 12+ domains, so that is one bill and one schema instead of five vendors.&lt;/li&gt;
&lt;li&gt;I wrote zero chart renderers. &lt;code&gt;@roxyapi/ui&lt;/code&gt; is about 90 MIT web components that take the API response and draw it.&lt;/li&gt;
&lt;li&gt;At 2,000 subscribers a cache-correct build uses about &lt;strong&gt;8,760 API calls a month&lt;/strong&gt;. The naive build uses &lt;strong&gt;120,000&lt;/strong&gt;, which is 5x the plan. Same product, 13x the load.&lt;/li&gt;
&lt;li&gt;Napkin math: 2,000 x $39/mo = $936K ARR, with the insight layer costing $389 a year. That is &lt;strong&gt;0.04 percent of revenue&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before the story, the facts, so you can bail early if this is not your problem.&lt;/p&gt;

&lt;p&gt;RoxyAPI is a typed REST API and Remote MCP server that computes verified astrology, Vedic, numerology, tarot, and human design results from one subscription key, with no AGPL licensing exposure. Building an AI astrology companion takes three layers working together: a verified calculation API for facts, a database for per-user memory, and an LLM for voice, where the calculation layer never does the talking and the LLM never does the maths.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What it is&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Typed REST API plus hosted Remote MCP for spiritual-wellness calculations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Domains&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12+ on one key: Western astrology, Vedic, forecast, human design, numerology, tarot, biorhythm, I Ching, crystals, dreams, angel numbers, location&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Endpoints&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;171+, exact live count in the public spec at &lt;code&gt;roxyapi.com/api/v2/openapi.json&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Accuracy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Median 1.5 arcseconds from NASA JPL Horizons DE441&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pricing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$39/month or $389/year, 25,000 requests, flat, 1 request equals 1 unit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Licence&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;In-house engine, no AGPL, commercial use on every plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;About 90 MIT web components, React and Vue wrappers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Figures current as of August 2026, and every one of them is checkable: the counts against the live spec above, the accuracy against the open benchmark linked further down.&lt;/p&gt;

&lt;p&gt;And the five steps, if you want the shape before the detail:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick a calculation API that is verified and licence-clean. Do not build the ephemeris.&lt;/li&gt;
&lt;li&gt;Store identity, birth data, and the computed chart in your own database. Once.&lt;/li&gt;
&lt;li&gt;Ground the LLM. The API computes, the model narrates, never the reverse.&lt;/li&gt;
&lt;li&gt;Add a forecast layer with significance scoring so the agent knows what to raise unprompted.&lt;/li&gt;
&lt;li&gt;Render with pre-built components instead of hand-drawing chart wheels.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why I stopped trying to compute the charts myself
&lt;/h2&gt;

&lt;p&gt;I gave the build-it-yourself route an honest three weeks, and the honest answer is that the first 70 percent is genuinely not that hard. You can get planetary positions out of an open source library in an afternoon and feel like a genius. The last 30 percent is where the months go, and it is the 30 percent nobody blogs about because it is not fun.&lt;/p&gt;

&lt;p&gt;Here is what actually ate me alive:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Timezones, historically.&lt;/strong&gt; Not "what is UTC offset now". What was the offset at that exact spot on that exact date in 1978, including whatever regional daylight-saving experiment was running that year. Get this wrong and every chart is wrong in a way that looks right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;House systems.&lt;/strong&gt; Placidus is the default everyone expects. It also degenerates above roughly 66 degrees latitude, because the maths runs out of solutions. Norway exists. Iceland exists. Your app will crash on them or, worse, quietly return nonsense.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification.&lt;/strong&gt; This is the real killer. Who tells you that you are correct? I was comparing against random websites. Random websites are not a test suite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Licensing.&lt;/strong&gt; The venerable library most legacy providers are built on top of is Swiss Ephemeris, which is AGPL. AGPL is copyleft that reaches through a network boundary, meaning a hosted SaaS is not a loophole. If you are building a commercial product, please go and read that licence properly before you &lt;code&gt;npm install&lt;/code&gt; your way into a problem. Proxying a commercially licensed API with no copyleft is the clean move here, and that is a legal reason, not a technical one.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Look, I have enormous respect for people who go and build ephemeris code from scratch. Truly. I just also think there is a specific kind of developer, and it me, who will spend nine months on that and ship absolutely nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I actually picked the insight API
&lt;/h2&gt;

&lt;p&gt;Full disclosure before the table, because you should assume bias and check my work: I ended up on RoxyAPI and I am obviously going to sound like a fan of the thing I chose. So do not take the right-hand column as a verdict. Take the left-hand column as the checklist, go run it against every vendor on your own shortlist, and make me argue for my pick.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What I checked&lt;/th&gt;
&lt;th&gt;Why it decides the build&lt;/th&gt;
&lt;th&gt;What I found&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Verification you can re-run&lt;/td&gt;
&lt;td&gt;A vendor accuracy claim you cannot audit is marketing&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://github.com/RoxyAPI/astrology-api-benchmark" rel="noopener noreferrer"&gt;Open MIT benchmark&lt;/a&gt;, 210 planet positions across 21 charts vs NASA JPL Horizons DE441. Clone it and check me&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Measured deviation&lt;/td&gt;
&lt;td&gt;"Accurate" is not a number&lt;/td&gt;
&lt;td&gt;Median 1.5 arcseconds (about 0.0004 degrees), p95 11.2, max 16.6 on Neptune, run dated 2026-07-30. Method at &lt;a href="https://roxyapi.com/methodology" rel="noopener noreferrer"&gt;/methodology&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Licensing&lt;/td&gt;
&lt;td&gt;Copyleft reaching into a commercial product&lt;/td&gt;
&lt;td&gt;In-house engine, no AGPL, commercial use on every plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pricing predictability&lt;/td&gt;
&lt;td&gt;Credit-weighted models make the bill track which endpoints users happen to hit&lt;/td&gt;
&lt;td&gt;Flat, 1 request equals 1 unit, every domain in every plan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Domain breadth on one key&lt;/td&gt;
&lt;td&gt;Stitching four vendors means four keys, four bills, four schemas&lt;/td&gt;
&lt;td&gt;12+ domains, 171+ endpoints, one subscription&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typed SDK&lt;/td&gt;
&lt;td&gt;Untyped JSON in an LLM pipeline is how you ship a 400 to production&lt;/td&gt;
&lt;td&gt;Generated from the live OpenAPI spec, so it cannot drift&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remote MCP&lt;/td&gt;
&lt;td&gt;Agent gets tools without me writing a tool layer&lt;/td&gt;
&lt;td&gt;Hosted Remote MCP per domain, no local process&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Try before paying&lt;/td&gt;
&lt;td&gt;Signup-gated sandboxes hide the response shape&lt;/td&gt;
&lt;td&gt;Live playground returning real production responses, no key needed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two that genuinely moved me were the &lt;strong&gt;re-runnable benchmark&lt;/strong&gt; and the &lt;strong&gt;flat pricing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On the benchmark: RoxyAPI planetary positions show a median deviation of 1.5 arcseconds from NASA JPL Horizons DE441, measured across 210 reference positions in 21 charts by an open MIT-licensed benchmark anyone can clone and re-run. I do not want to be told a provider is accurate, I want to clone a repo and check. The fact that the worst single deviation is published, and that it is Neptune (the slowest body in the set, so the hardest to pin), told me more about their honesty than any homepage headline. Publishing your worst number is a flex only correct people can do.&lt;/p&gt;

&lt;p&gt;On pricing, let me show you the actual arithmetic, because this is where I nearly made an expensive mistake.&lt;/p&gt;

&lt;p&gt;Several providers in this category price on a wallet. The headline is a small monthly number, and it looks unbeatable next to a $39 plan. Read the second paragraph though: on the ones I shortlisted, the plan price &lt;strong&gt;is&lt;/strong&gt; your credit balance, and every call deducts somewhere between $0.008 and $0.05 depending on which internal engine serves it. So a $12 plan is not a $12 plan, it is 240 to 1,500 calls, depending on a routing decision you do not control.&lt;/p&gt;

&lt;p&gt;(Those per-call rates are read straight off published pricing pages in this category, checked August 2026. I am not naming vendors, but you can verify the pattern in about five minutes on any of their pricing pages. Look for the words "wallet", "credits", or a per-query rate range.)&lt;/p&gt;

&lt;p&gt;Run my actual load through that. Same 25,000 requests:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pricing model&lt;/th&gt;
&lt;th&gt;Cost for 25,000 calls&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wallet, cheapest published rate ($0.008)&lt;/td&gt;
&lt;td&gt;$200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wallet, mid published rate ($0.03)&lt;/td&gt;
&lt;td&gt;$750&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wallet, top published rate ($0.05)&lt;/td&gt;
&lt;td&gt;$1,250&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flat plan, 1 request equals 1 unit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$39&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Five to thirty-two times the price for the identical call count, and I cannot tell you in advance which end of that range I land on, because it depends on which engine my users happen to trigger. The cheap headline is real. It is just the entry price, not the running cost.&lt;/p&gt;

&lt;p&gt;That is the thing about credit models: your bill stops tracking how many users you have and starts tracking which features they poke. Try forecasting revenue against that. You cannot. I have tried. I made a spreadsheet. The spreadsheet made me sad.&lt;/p&gt;

&lt;p&gt;One more thing worth doing on any vendor before you sign: count the endpoints yourself. I found homepage operation counts on more than one site that did not survive contact with the route inventory published on that same site a page later, and at least one catalog that carried an explicit note saying the listing was not a claim that any of it was actually callable. Not naming names, and I am not saying anyone is lying. I am saying the number on the marketing page and the number of things you can call are different numbers, and only one of them is yours to build on. Ask for the live spec.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to try this?&lt;/strong&gt; The &lt;a href="https://roxyapi.com/api-reference" rel="noopener noreferrer"&gt;live API playground&lt;/a&gt; returns real production responses with no signup and no key, so you can check the exact JSON shape before you commit to anything. &lt;a href="https://roxyapi.com/pricing" rel="noopener noreferrer"&gt;Pricing is here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack, and the one rule that holds it together
&lt;/h2&gt;

&lt;p&gt;Next.js 16 App Router on Vercel, Supabase for Postgres plus auth plus row level security plus pgvector, the Vercel AI SDK for the chat loop, and &lt;code&gt;@roxyapi/sdk&lt;/code&gt; for every calculation. Boring, deliberately. I wanted zero novel infrastructure so all my remaining brain could go into the product.&lt;/p&gt;

&lt;p&gt;The rule that holds it together, and I will say this louder for the people at the back:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The LLM does voice. The API does maths. They never swap jobs.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ask an LLM where Saturn was on a specific date and it will answer, confidently, with a completely invented degree. It is not lying, it is autocompleting. It has seen a million sentences shaped like "Saturn was at 12 degrees Capricorn" and it will happily produce another one. Astrology output is uniquely dangerous here because there is no visible failure. A hallucinated chart looks exactly like a real chart.&lt;/p&gt;

&lt;p&gt;So the flow is: compute real facts, hand them to the model as grounded context, and let the model be warm and human about facts it did not make up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User message
     |
     v
Supabase  --&amp;gt;  stored natal chart (computed once, immutable)
     |         reading history (what we already told them)
     |         pgvector recall (themes, not rows)
     v
RoxyAPI   --&amp;gt;  live time-dependent facts only (today, this window)
     |
     v
LLM       --&amp;gt;  voice, warmth, continuity. Zero arithmetic.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install is unremarkable, which is the point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @roxyapi/sdk @supabase/supabase-js ai
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why I bought the maths and built the AI myself
&lt;/h2&gt;

&lt;p&gt;Several providers in this space sell an "ask a question, get an answer" endpoint. You post a natural-language question plus birth details, they return prose. It demos beautifully. I came very close to buying one, and I want to explain why I did not, because I think this is the single most important architectural call in the whole build.&lt;/p&gt;

&lt;p&gt;The AI layer is the part you can genuinely build in an afternoon. This is the entire chat loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;streamText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;anthropic&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ai-sdk/anthropic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude-sonnet-5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myVoice&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                 &lt;span class="c1"&gt;// MY product personality, not a vendor default&lt;/span&gt;
  &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                        &lt;span class="c1"&gt;// MY conversation history from Supabase&lt;/span&gt;
  &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;roxyTools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                &lt;span class="c1"&gt;// grounded facts, computed not guessed&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is it. That is the thing being sold as the premium tier. Four reasons I would not rent it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Voice is the product.&lt;/strong&gt; My companion has a personality users came back for. A hosted answer endpoint returns the vendor voice, and I cannot tune it, translate its tone, or make it funnier at 11pm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I pay the model markup twice.&lt;/strong&gt; Per-query AI pricing is a margin on tokens I can buy directly from Anthropic or OpenAI at cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I cannot swap models.&lt;/strong&gt; When something cheaper or smarter ships, I want to change one line that afternoon. Renting the AI layer means waiting for someone else to upgrade.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It does not know my user.&lt;/strong&gt; The whole point of this post is memory. A stateless question endpoint has none, and the vendor should not be holding my user history anyway.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now the honest counter, because there is one: if you do not care about voice, do not want to run an LLM at all, and want a reading on screen this week, a hosted AI endpoint genuinely ships faster. That is a real trade and for some products it is the right one. If your differentiator is the relationship, it is the wrong one.&lt;/p&gt;

&lt;p&gt;Here is the asymmetry that settles it. &lt;strong&gt;The LLM layer is an afternoon. The ephemeris layer is not.&lt;/strong&gt; I can wire a model, tools, and streaming before dinner. I cannot build arc-second planetary positions, historical timezone resolution, house systems that survive polar latitudes, and a benchmark proving all of it, in any amount of afternoons. So buy the part that is genuinely hard and verifiable, and own the part that is your product. Renting it the other way round is backwards, and it is also the more expensive half.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-user memory: stateless calculation, stateful you
&lt;/h2&gt;

&lt;p&gt;The thing that turns a horoscope toy into something people pay for monthly is memory. Not "remembers your sign". Remembers that six weeks ago it told you a career transit was coming, and now asks how that went.&lt;/p&gt;

&lt;p&gt;The architecture insight is a clean ownership split. The API is stateless on purpose, it computes from inputs and keeps nothing, which means every piece of personal continuity lives in my database where I control privacy and nobody can hold it hostage.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Who owns it&lt;/th&gt;
&lt;th&gt;What it holds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Calculation&lt;/td&gt;
&lt;td&gt;RoxyAPI (stateless)&lt;/td&gt;
&lt;td&gt;charts, transits, forecast, numerology&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Identity and birth data&lt;/td&gt;
&lt;td&gt;My Supabase&lt;/td&gt;
&lt;td&gt;user, birth date/time, resolved location&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reading history&lt;/td&gt;
&lt;td&gt;My Supabase&lt;/td&gt;
&lt;td&gt;every reading I have shown, timestamped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Semantic recall&lt;/td&gt;
&lt;td&gt;Supabase pgvector&lt;/td&gt;
&lt;td&gt;embeddings of past readings and chat&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two Supabase tables carry almost all of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt;     &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="k"&gt;cascade&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;birth_date&lt;/span&gt;  &lt;span class="nb"&gt;date&lt;/span&gt;        &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;birth_time&lt;/span&gt;  &lt;span class="nb"&gt;time&lt;/span&gt;        &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;latitude&lt;/span&gt;    &lt;span class="nb"&gt;double&lt;/span&gt; &lt;span class="nb"&gt;precision&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;longitude&lt;/span&gt;   &lt;span class="nb"&gt;double&lt;/span&gt; &lt;span class="nb"&gt;precision&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;timezone&lt;/span&gt;    &lt;span class="nb"&gt;text&lt;/span&gt;        &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;     &lt;span class="c1"&gt;-- IANA string, e.g. "Europe/Lisbon"&lt;/span&gt;
  &lt;span class="n"&gt;natal_chart&lt;/span&gt; &lt;span class="n"&gt;jsonb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                    &lt;span class="c1"&gt;-- computed ONCE, never again&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt;  &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;readings&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;         &lt;span class="n"&gt;bigserial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;user_id&lt;/span&gt;    &lt;span class="n"&gt;uuid&lt;/span&gt; &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="k"&gt;cascade&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;kind&lt;/span&gt;       &lt;span class="nb"&gt;text&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;-- 'daily' | 'timeline' | 'chat'&lt;/span&gt;
  &lt;span class="n"&gt;payload&lt;/span&gt;    &lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;-- the structured API response&lt;/span&gt;
  &lt;span class="n"&gt;shown&lt;/span&gt;      &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                      &lt;span class="c1"&gt;-- what the user actually read&lt;/span&gt;
  &lt;span class="n"&gt;embedding&lt;/span&gt;  &lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1536&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;timestamptz&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;profiles&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="k"&gt;security&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;readings&lt;/span&gt; &lt;span class="n"&gt;enable&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="k"&gt;security&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Turn on RLS. I am putting that in its own sentence because I forgot for two days and it is the kind of thing that ends a startup rather than inconveniencing it.&lt;/p&gt;

&lt;p&gt;Now the single most important function in the codebase, and it is twelve lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRoxy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@roxyapi/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;roxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRoxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ROXY_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ensureChart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;birth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BirthData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profiles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;natal_chart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;single&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;natal_chart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;natal_chart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;          &lt;span class="c1"&gt;// 0 API calls, forever&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chart&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;roxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;astrology&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateNatalChart&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;birth&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;profiles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;natal_chart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chart&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                          &lt;span class="c1"&gt;// exactly 1 billable call, ever&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Birth data is immutable. A natal chart computed from immutable inputs is therefore also immutable. It does not expire, it does not go stale, it does not need a TTL. Cache it until the heat death of the universe. If you take one thing from this post, take this one, because it is the difference between the two cost columns further down.&lt;/p&gt;

&lt;p&gt;And geocode the city once, at signup, rather than making a human type coordinates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;roxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchCities&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Lisbon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timezone&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="c1"&gt;// =&amp;gt; { latitude: 38.71667, longitude: -9.13333, timezone: "Europe/Lisbon", utcOffset: 1, ... }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That IANA string is doing heavy lifting. Pass &lt;code&gt;"Europe/Lisbon"&lt;/code&gt; and the server resolves the daylight-saving-correct offset for the birth date itself. Pass a hardcoded number and you have quietly reintroduced problem 1 from the top of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  The forecast timeline is the companion heartbeat
&lt;/h2&gt;

&lt;p&gt;Here is the endpoint that made the product feel alive, and honestly the reason I stopped shopping around.&lt;/p&gt;

&lt;p&gt;A companion needs to know what is coming up for &lt;strong&gt;this specific person&lt;/strong&gt;, ranked by how much it matters, so it can bring things up unprompted. That is one call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;timeline&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;roxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateTimeline&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;birthData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1994-03-11&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;08:20:00&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Europe/Lisbon&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                 &lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;38.71667&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;9.13333&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;startDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-08-01&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;endDate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2026-09-15&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;domains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;western&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vedic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;biorhythm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;minSignificance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real response from production, trimmed for length, nothing fabricated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"events"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"datetime"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-08-12T17:45:46Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"western"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eclipse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sun"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"kind"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"total solar eclipse as the New Moon aligns with the Sun, a major timing event that opens a new chapter."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"significance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;95&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-09"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"domain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"western"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"transit-aspect"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Pluto"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Venus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"aspect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sextile"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"orb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"significance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to notice.&lt;/p&gt;

&lt;p&gt;That eclipse is real. &lt;a href="https://science.nasa.gov/eclipses/future-eclipses/total-solar-eclipse-on-august-12-2026/" rel="noopener noreferrer"&gt;NASA has it&lt;/a&gt;: 12 August 2026, totality across Greenland, Iceland and northern Spain, the first total solar eclipse touching mainland Europe since 1999. The API refined it to the exact instant by search rather than reporting a daily sample point, which is the kind of detail I did not know I needed until I had it.&lt;/p&gt;

&lt;p&gt;And &lt;code&gt;significance&lt;/code&gt; is scored 0 to 100, which is the field I built the entire notification system on. Above 85, the companion messages you first. Below 70, I do not even fetch it. One integer replaced a ranking heuristic I had been dreading writing.&lt;/p&gt;

&lt;p&gt;Cross-domain in one call matters more than it sounds: Western transits, Vedic dasha boundaries and biorhythm critical days merged into a single sorted timeline. I priced out stitching that from separate vendors. It was three integrations and three bills.&lt;/p&gt;

&lt;h2&gt;
  
  
  One conversation, 12+ domains, one key
&lt;/h2&gt;

&lt;p&gt;This is the part I did not see coming, and it ended up being the reason people stayed.&lt;/p&gt;

&lt;p&gt;Users do not stay inside one system. A real conversation with my companion goes: "how does my week look" (transits), then "pull me a card for that" (tarot), then "wait, what is my human design type" (bodygraph), then "cast me a hexagram on the job thing" (I Ching), then "what is my life path number again" (numerology). Five domains in about ninety seconds, because that is genuinely how people who are into this stuff think.&lt;/p&gt;

&lt;p&gt;The naive version of that is five vendors. Five contracts, five API keys, five auth schemes, five response shapes to normalise, five invoices, and five separate things that can go down at 2am. I costed it. It was miserable, and worse, it makes the agent tool layer a nightmare because every provider disagrees about what a "reading" looks like.&lt;/p&gt;

&lt;p&gt;The version I shipped is one namespace per domain on one key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;life&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="nx"&gt;roxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forecast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateTimeline&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;birthData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;birth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;domains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;western&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;roxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tarot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDailyCard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;roxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;humanDesign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateBodygraph&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;birth&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;roxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iching&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;castReading&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="nx"&gt;roxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;numerology&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;calculateLifePath&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1994&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same key. Same bill. Same error handling. Real values from that exact run:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Actual response&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tarot&lt;/td&gt;
&lt;td&gt;&lt;code&gt;getDailyCard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;card.name&lt;/code&gt; = "Page of Pentacles", reversed, plus &lt;code&gt;dailyMessage&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Human Design&lt;/td&gt;
&lt;td&gt;&lt;code&gt;generateBodygraph&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;type&lt;/code&gt; = "Manifesting Generator", &lt;code&gt;authority&lt;/code&gt; = "Sacral", &lt;code&gt;profile&lt;/code&gt; = "4/6"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I Ching&lt;/td&gt;
&lt;td&gt;&lt;code&gt;castReading&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hexagram 25 "Innocence (The Unexpected)", changing lines 3, 4, 6, becoming 63 "After Completion"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Numerology&lt;/td&gt;
&lt;td&gt;&lt;code&gt;calculateLifePath&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;number&lt;/code&gt; = 1, &lt;code&gt;type&lt;/code&gt; = "single"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forecast&lt;/td&gt;
&lt;td&gt;&lt;code&gt;generateTimeline&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the significance-scored event list from the section above&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note the &lt;code&gt;seed&lt;/code&gt; on tarot and I Ching. Pass a stable per-user seed and the same user gets the same card for the same day across devices and refreshes, which matters enormously because a tarot app that reshuffles when you hit reload is not a tarot app, it is a random number generator with nice artwork.&lt;/p&gt;

&lt;p&gt;For the agent layer this gets better. Rather than hand-writing a tool definition per endpoint, you can point the model at the hosted &lt;a href="https://roxyapi.com/docs/mcp" rel="noopener noreferrer"&gt;Remote MCP servers&lt;/a&gt; (Model Context Protocol, the standard that lets an AI agent discover and call tools without a hand-maintained registry) and it works out what it can call on its own. RoxyAPI runs one Remote MCP server per domain over Streamable HTTP, so there is no local process to babysit. I deleted a tool-registration file I had been maintaining by hand and did not miss it.&lt;/p&gt;

&lt;p&gt;All 12+ domains ship on every plan, and I pay the same whether I call one or all of them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Western astrology&lt;/li&gt;
&lt;li&gt;Vedic astrology&lt;/li&gt;
&lt;li&gt;Forecast&lt;/li&gt;
&lt;li&gt;Human design&lt;/li&gt;
&lt;li&gt;Numerology&lt;/li&gt;
&lt;li&gt;Tarot&lt;/li&gt;
&lt;li&gt;Biorhythm&lt;/li&gt;
&lt;li&gt;I Ching&lt;/li&gt;
&lt;li&gt;Crystals&lt;/li&gt;
&lt;li&gt;Dreams&lt;/li&gt;
&lt;li&gt;Angel numbers&lt;/li&gt;
&lt;li&gt;Location and timezone&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I actively use seven. The other five cost me nothing to leave sitting there until a user asks, which is the entire argument for breadth on a flat plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  I did not write a single chart renderer
&lt;/h2&gt;

&lt;p&gt;Confession: I cannot draw a natal chart wheel. I do not know the glyphs. I would have spent a fortnight on an SVG that a real astrologer takes one look at and says the aspect lines are wrong.&lt;/p&gt;

&lt;p&gt;So I did not. &lt;code&gt;@roxyapi/ui&lt;/code&gt; is a set of MIT-licensed web components that take an API response and render it. About 90 of them. Framework agnostic, with React and Vue wrappers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.jsdelivr.net/npm/@roxyapi/ui@0/dist/cdn/roxy-ui.js"&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;roxy-natal-chart&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"wheel"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/roxy-natal-chart&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"module"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="c1"&gt;// the chart you already cached in Supabase. No refetch, no extra billable call.&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wheel&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;chartFromYourServer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole integration. The ones I use are &lt;code&gt;&amp;lt;roxy-natal-chart&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;roxy-forecast-timeline&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;roxy-tarot-spread&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;roxy-bodygraph&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;roxy-hexagram&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;roxy-numerology-card&amp;gt;&lt;/code&gt;, and there are equivalents for Vedic kundli, dashas, panchang, biorhythm and the rest.&lt;/p&gt;

&lt;p&gt;The bit I want to underline for anyone doing the build-versus-buy sum: this is not just saved frontend hours, it is saved &lt;strong&gt;domain-expert&lt;/strong&gt; hours. Rendering a bodygraph correctly requires knowing what a defined centre looks like. Rendering a kundli requires knowing that North Indian and South Indian charts are laid out differently. That is not frontend work, that is specialist work, and I do not have it. Full component catalogue is at &lt;a href="https://roxyapi.com/ui" rel="noopener noreferrer"&gt;/ui&lt;/a&gt;, and if you are dropping a reading onto a site with no build step at all there are copy-paste &lt;a href="https://roxyapi.com/widgets" rel="noopener noreferrer"&gt;widgets&lt;/a&gt; that use a browser-safe publishable key instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The napkin math that decides if this is a business
&lt;/h2&gt;

&lt;p&gt;Right, the number in the title. I am not telling you I have hit $1M ARR, because I have not, and dev.to would correctly eat me alive for claiming it. What I am showing you is the model I built against, because the architecture only works if the unit economics work, and this is the part nobody publishes.&lt;/p&gt;

&lt;p&gt;Target shape: &lt;strong&gt;2,000 subscribers at $39/month equals $936,000 ARR.&lt;/strong&gt; Fine. Achievable-ish. Now, what does the insight layer cost at that scale?&lt;/p&gt;

&lt;p&gt;This is where the immutability thing pays off. Monthly call volume at 2,000 subscribers, assuming 200 new signups a month:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Call type&lt;/th&gt;
&lt;th&gt;Frequency&lt;/th&gt;
&lt;th&gt;Calls/month&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Natal chart&lt;/td&gt;
&lt;td&gt;New users only, cached forever&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Location lookup&lt;/td&gt;
&lt;td&gt;New users only&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forecast timeline&lt;/td&gt;
&lt;td&gt;1 per user per month&lt;/td&gt;
&lt;td&gt;2,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Daily horoscope&lt;/td&gt;
&lt;td&gt;12 signs x 30 days, &lt;strong&gt;shared by everyone&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;360&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent tool calls&lt;/td&gt;
&lt;td&gt;~3 per user per month, uncached&lt;/td&gt;
&lt;td&gt;6,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8,760&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Starter plan includes 25,000 requests a month. So the whole thing runs at about 35 percent of the entry tier, with 16,240 requests of headroom, for &lt;strong&gt;$389 a year&lt;/strong&gt; on annual billing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API spend as a share of revenue: &lt;strong&gt;0.04 percent&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;API cost per subscriber per year: &lt;strong&gt;$0.195&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Cost per request: about &lt;strong&gt;$0.0013&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the version where I had not thought about caching. Recompute the chart on every session, twice a day per user: 2,000 x 30 x 2 equals &lt;strong&gt;120,000 calls a month&lt;/strong&gt;, which is 5x the plan allowance and roughly 13x the traffic for a product that is byte-for-byte identical from the user side.&lt;/p&gt;

&lt;p&gt;Same app. Same features. One of them is a business and the other is a bonfire. The difference is &lt;code&gt;ensureChart&lt;/code&gt; and about four hours of thinking.&lt;/p&gt;

&lt;p&gt;The other quiet win: the daily horoscope row. Twelve signs times thirty days is 360 calls that serve all 2,000 users, or all 200,000 users, because a Pisces daily horoscope is the same Pisces daily horoscope for everybody. Cache by &lt;code&gt;(sign, date)&lt;/code&gt; and that line never grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three-minute bug that nearly shipped
&lt;/h2&gt;

&lt;p&gt;I want to end on the thing that scared me most, because it is a lovely demonstration of why "looks fine" is not a test.&lt;/p&gt;

&lt;p&gt;My signup form collected birth time with a rounded minute picker. Seemed harmless. Then I ran the same chart three minutes apart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;08:20:00  -&amp;gt;  Pisces  29.32 deg   (longitude 359.325)
08:23:00  -&amp;gt;  Aries    0.58 deg   (longitude   0.581)
08:25:00  -&amp;gt;  Aries    1.42 deg   (longitude   1.419)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three minutes. The rising sign changed. Pisces to Aries. In this chart the ascendant moved 1.26 degrees in those three minutes, and it happened to be sitting 0.675 degrees from the boundary, so the whole chart interpretation flips to a different sign and every house cusp shifts with it. The rate varies with latitude and time of year, but the lesson does not.&lt;/p&gt;

&lt;p&gt;Nobody would have caught this. Not one user. It renders identically, it reads plausibly, and it is completely wrong for anyone born near a cusp. The only reason I caught it is that I was logging the raw &lt;code&gt;degree&lt;/code&gt; next to the sign, saw 29.32 sitting right on the edge, and thought, hang on. If you are only storing the sign name, go and add the degree to your logs today. That is the whole lesson.&lt;/p&gt;

&lt;p&gt;That is the entire argument for grounding, in one example. An LLM would have told me Pisces, or Aries, with total confidence, and never once mentioned that it was a coin flip.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is the best astrology API for building an AI companion?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;RoxyAPI, for this specific use case, because the companion pattern needs three things together: verified calculations you can audit, a cross-domain forecast timeline with significance scoring so the agent knows what to raise, and a stateless design so per-user memory stays in your own database. It covers 12+ insight domains and 171+ endpoints on one key with flat pricing, and the accuracy claim is backed by a re-runnable public benchmark against NASA JPL Horizons rather than a marketing line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I just ask ChatGPT or Claude to calculate a birth chart?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No, and this is the single most expensive mistake in the category. LLMs generate plausible planetary positions rather than computing them, and a wrong chart is visually indistinguishable from a correct one, so the bug reaches production and stays there. Ground the model on real calculations from RoxyAPI and let it handle interpretation and tone instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does RoxyAPI store my users birth data or conversations?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. The API is stateless and keeps no charts, profiles, or history. It computes from the inputs you send and returns a result, so every piece of personal data lives in your own database and vector store. That is what makes the memory layer yours and keeps the privacy story simple.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How many API calls does an app like this actually need?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Far fewer than people assume, if you cache correctly. Because birth data is immutable, a natal chart is one call per user for the lifetime of the account, and daily horoscopes are shared across all users by sign and date. The model above runs 2,000 subscribers on about 8,760 calls a month, comfortably inside the 25,000 included in the $39 Starter plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is there one API that covers astrology, tarot, numerology, and human design together?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. RoxyAPI covers 12+ domains on a single key and a single subscription: Western astrology, Vedic, forecast, human design, numerology, tarot, biorhythm, I Ching, crystals, dreams, angel numbers, and location. For an AI companion this matters more than it sounds, because users switch systems mid-conversation and stitching four vendors means four keys, four schemas, and four bills. Every domain is included in every plan with no per-domain surcharge, and adding one later is a new method call rather than a new contract. On the $389 annual Starter plan that works out at about $2.70 per domain per month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I have to build the chart wheel and bodygraph UI myself?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. &lt;code&gt;@roxyapi/ui&lt;/code&gt; ships about 90 MIT-licensed web components that render an API response directly, including the natal wheel, Vedic kundli, human design bodygraph, tarot spreads, forecast timeline, and I Ching hexagrams, with React and Vue wrappers. It is framework agnostic and renders client-side from data you already cached, so it costs no extra API calls. That removes the need for a frontend developer who also knows how to draw a bodygraph correctly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need an AGPL licence to build a commercial astrology product?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not if your calculation layer is not AGPL. Many legacy providers are built over Swiss Ephemeris, which is AGPL, and that copyleft reaches through a network boundary so hosting it as a service is not an exemption. RoxyAPI runs its own engine with commercial use included on every plan, so there is no copyleft obligation flowing into your codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I landed
&lt;/h2&gt;

&lt;p&gt;Ship the memory layer, ground the model, cache the immutable stuff, and let someone else own the arcseconds. The astronomy is not your product. The relationship your app builds with the person using it is your product, and every hour spent on planetary positions is an hour not spent on that.&lt;/p&gt;

&lt;p&gt;If you want the deeper versions of what I sketched here, the &lt;a href="https://roxyapi.com/docs/tutorials/ai-companion-with-memory" rel="noopener noreferrer"&gt;AI companion with memory tutorial&lt;/a&gt; covers the recall layer properly and &lt;a href="https://roxyapi.com/docs/tutorials/complete-birth-profile" rel="noopener noreferrer"&gt;complete birth profile&lt;/a&gt; shows the full cross-domain fan-out including the trick of stripping editorial prose before it reaches the model. The &lt;a href="https://github.com/RoxyAPI/astrology-ai-chatbot" rel="noopener noreferrer"&gt;chatbot starter&lt;/a&gt; is MIT and clonable if you would rather begin from something that already runs.&lt;/p&gt;

&lt;p&gt;Now if you will excuse me, I have some mugs to wash.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>typescript</category>
      <category>ai</category>
      <category>supabase</category>
    </item>
    <item>
      <title>How to choose an astrology API: build vs buy, MCP, and the licensing trap nobody mentions</title>
      <dc:creator>Nick</dc:creator>
      <pubDate>Wed, 17 Jun 2026 03:31:14 +0000</pubDate>
      <link>https://dev.to/prowler64/how-to-choose-an-astrology-api-build-vs-buy-mcp-and-the-licensing-trap-nobody-mentions-3f2b</link>
      <guid>https://dev.to/prowler64/how-to-choose-an-astrology-api-build-vs-buy-mcp-and-the-licensing-trap-nobody-mentions-3f2b</guid>
      <description>&lt;p&gt;I spent the back half of last year shipping an AI app that wires five systems into one chat UI: natal charts, vedic kundli, numerology, human design, and tarot. I am a developer, not an astrologer. So the part I actually had to get right was not the readings, it was the build-versus-buy call on the calculation layer underneath them.&lt;/p&gt;

&lt;p&gt;I went in assuming this was a quick decision. Pick the cheapest provider, move on. It was not. The threads I found were all "just use Swiss Ephemeris" or "here are the top 10 horoscope APIs," and none of them finished the sentence on the things that bit me later. This is the checklist I wish I had read first, in the order that actually matters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The calculation engine and its license matter more than the feature list. A lot of providers wrap a 1990s desktop library that carries an AGPL obligation into your closed-source app.&lt;/li&gt;
&lt;li&gt;Endpoint counts are mostly marketing. Ask for a live OpenAPI spec you can open and count, and count real systems, not output formats.&lt;/li&gt;
&lt;li&gt;For an AI app, hosted (remote) MCP versus a local server you babysit is a real time difference, not a nice-to-have.&lt;/li&gt;
&lt;li&gt;Get structured JSON back, not a prose blob, so you own the interpretation layer.&lt;/li&gt;
&lt;li&gt;Flat pricing where one request equals one unit keeps your bill boring. Credit wallets do not.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start with the calculation engine, not the feature list
&lt;/h2&gt;

&lt;p&gt;Look at what computes the math before you look at anything else, because that is the one decision you cannot cheaply reverse. Here is the thing almost nobody says out loud: most established astrology APIs are wrappers around the same library, Swiss Ephemeris, a C library from the late 1990s originally written for single-user desktop astrology software. When a provider advertises "Swiss Ephemeris precision," that is the actual claim. They wrapped a 25-year-old desktop library and put a REST endpoint in front of it.&lt;/p&gt;

&lt;p&gt;Two problems with betting an AI product on that in 2026.&lt;/p&gt;

&lt;p&gt;The license is the big one. Swiss Ephemeris is AGPL-3.0 unless you buy a commercial license. The AGPL network-use clause means a closed-source SaaS built on it can be obligated to open-source the whole app. Most people repeating "just use Swiss Ephemeris" have never opened the LICENSE file, and this is the kind of thing that surfaces during due diligence or an acquisition, not at a convenient time. Either pay for the commercial license, open-source your product, or use an API whose engine is not AGPL. Decide before you write code.&lt;/p&gt;

&lt;p&gt;The second problem is fit. Desktop, single user, one chart at a time. It was never designed for APIs, serverless, or agents calling tools in parallel.&lt;/p&gt;

&lt;p&gt;That combination is why I stopped trying to self-host an engine. RoxyAPI runs its own, called Roxy Ephemeris, built for APIs and agents, with no AGPL strings attached. The part that earned my trust was that they publish the proof instead of asking for faith. There is an open, MIT-licensed benchmark at &lt;a href="https://github.com/RoxyAPI/astrology-api-benchmark" rel="noopener noreferrer"&gt;github.com/RoxyAPI/astrology-api-benchmark&lt;/a&gt; comparing 210 planet positions across 21 charts against NASA JPL Horizons, which is the physics ground truth rather than another library copying another library. The published median difference is about 16 arc-seconds, max around 32, and the Moon comes in tightest at roughly 3. I re-ran a couple of my own birth dates before I trusted any of it. Most providers say "NASA-grade" and hope you never ask for the numbers.&lt;/p&gt;

&lt;p&gt;If you want to poke at real responses before reading further, &lt;a href="https://roxyapi.com/api-reference" rel="noopener noreferrer"&gt;roxyapi.com/api-reference&lt;/a&gt; is a live playground with a pre-filled test key. It returns real production data, not a fake sandbox, so you can audit before you pay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Count real domains, not formats
&lt;/h2&gt;

&lt;p&gt;When a provider leads with "300+ endpoints across 8 domains," slow down and check how that number is built, because two tricks show up over and over.&lt;/p&gt;

&lt;p&gt;First, the big endpoint number is often backed by a Postman collection rather than a live spec, so the truly callable count is a fraction of the headline.&lt;/p&gt;

&lt;p&gt;Second, and this is the one that fooled me at first, the domain count gets padded by slicing one system into many. Horoscopes, natal charts, compatibility, and synastry are all astrology. Counted separately they become four "domains," and a love calculator or a PDF report gets the same treatment. RoxyAPI keeps all of that under a single &lt;code&gt;astrology&lt;/code&gt; domain, the way a developer would actually group it. So a dozen honest domains can look smaller on a marketing page than a padded "eight." That is the whole trick: a provider sitting on one real system can out-breadth a genuine multi-domain platform on paper, and a non-technical buyer cannot tell the difference. Open the spec and the padding disappears.&lt;/p&gt;

&lt;p&gt;The check that worked for me: ask for a live OpenAPI spec, an actual JSON URL you can open and count, and count genuinely distinct systems instead of formats.&lt;/p&gt;

&lt;p&gt;One more thing to watch in the spec itself. A few providers hand-roll their OpenAPI file by hand, and the request schema in the doc does not match what the endpoint actually accepts. You copy the documented body, send it, and get a 400 back, with no clue why. Not cool, and a real trust killer the first time it happens. A spec that is generated from the running service cannot drift like that. RoxyAPI publishes one per domain, generated from production, so what you read is what the endpoint takes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# count callable endpoints and tag groups, no auth needed&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://roxyapi.com/api/v2/openapi.json | jq &lt;span class="s1"&gt;'{endpoints: (.paths|length), tags: (.tags|length)}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I ran that it returned around 150 endpoints across roughly a dozen real domains, and the per-domain specs at &lt;code&gt;/api/v2/{slug}/openapi.json&lt;/code&gt; match what production actually serves. That is the number I could plan against, not a Postman export that drifts from the live API. One call returns a complete natal chart instead of fragmenting it across ten separately billed endpoints, which also keeps the bill honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check how the API talks to AI agents
&lt;/h2&gt;

&lt;p&gt;If you are building anything agentic, look for remote (hosted) MCP, not a local stdio server you run yourself, because that gap is an afternoon of work per machine. RoxyAPI exposes a hosted Model Context Protocol server per domain over Streamable HTTP. In Claude Code I registered one with a single command and it was callable in seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add-json &lt;span class="nt"&gt;--scope&lt;/span&gt; user roxy-astrology &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'{"type":"http","url":"https://roxyapi.com/mcp/astrology","headers":{"X-API-Key":"YOUR_KEY"}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, every endpoint in that domain shows up as a tool. I can ask the agent "build me a React birth chart page" and it picks the right endpoint, reads the response shape, and wires it in. The alternative most places offer is a local server you clone, Docker, and keep alive yourself, which is fine on your laptop and miserable across a team or a CI box.&lt;/p&gt;

&lt;p&gt;The reason I care beyond convenience: a hosted MCP server lets my own agent ground its answers in verified calculations and reply in my product's voice, instead of me renting someone else's locked AI wrapper with a hidden system prompt. I bring my own model. The API brings the facts. That split is the whole point.&lt;/p&gt;

&lt;p&gt;There is a second piece that made this the fastest integration I have done. Roxy ships an &lt;code&gt;AGENTS.md&lt;/code&gt; playbook, bundled inside each SDK and also served at the site root, plus a machine-readable &lt;code&gt;llms.txt&lt;/code&gt;. So I barely hand-held the agent at all. I pointed Claude Code at the &lt;code&gt;AGENTS.md&lt;/code&gt;, it read the rules, connected the MCP server, and had working calls in a few minutes. For getting an insight platform running inside an AI agent, that is about as close to zero setup as it gets right now.&lt;/p&gt;

&lt;p&gt;It also shows up in the design, and this was a real differentiator once I noticed it. A lot of providers in this space are older astrology services that bolted MCP and "AI support" onto a REST API from years ago, after the hype landed. You can feel it: the AI layer sits next to the product instead of inside it. Roxy was built agent-first, so the MCP servers, the structured responses, and the agent docs are one surface, not three afterthoughts stapled on later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Look at the response shape before you commit
&lt;/h2&gt;

&lt;p&gt;Pull one real response and check whether you get discrete fields or a paragraph, because that single difference decides how much of your product you actually own. A weak API hands back prose you parse with regex and pray the format never changes. A good one hands back typed fields you read directly. Here is an actual daily horoscope response shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sign"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Leo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-22"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"overview"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The Moon activates your first house of identity..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"love"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The Moon stirs Leo with deep emotional awareness..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"career"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mars powers up your tenth house of career..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"energyRating"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"luckyNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"luckyColor"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gold"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"moonPhase"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Waxing Crescent Moon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"compatibleSigns"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Aries"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sagittarius"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gemini"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My app reads &lt;code&gt;energyRating&lt;/code&gt; or &lt;code&gt;compatibleSigns&lt;/code&gt; straight off the object. I can ship the built-in interpretation text when I want speed, or feed only the primitives into my own model when I want my own voice, so it stops hallucinating charts. There is also localized text on translated endpoints. Adding &lt;code&gt;?lang=es&lt;/code&gt; returns the same reading in Spanish without me touching anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://roxyapi.com/api/v2/astrology/horoscope/leo/daily?lang=es"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$ROXY_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What you want to avoid is a provider that returns one fixed prose report with nothing structured underneath. You cannot recompose it, localize it, or run your own model over it, so you are stuck with their wording forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing: flat versus credit wallets
&lt;/h2&gt;

&lt;p&gt;Check whether one request costs one unit or whether different endpoints burn different amounts of credits, because the second model gets expensive in ways you cannot forecast. My app needs five systems. A lot of providers bill per product, so that is five separate subscriptions, or they bill per-call credits where a PDF or a "premium" endpoint quietly costs 50x a normal call. RoxyAPI is one flat subscription that includes every domain, the remote MCP servers, the SDKs, and the i18n, with no per-product fee and no credit weighting.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;What you pay&lt;/th&gt;
&lt;th&gt;What happens as you grow&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Per-product subscriptions&lt;/td&gt;
&lt;td&gt;One sub per domain&lt;/td&gt;
&lt;td&gt;Five systems means five bills to reconcile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Credit wallet&lt;/td&gt;
&lt;td&gt;Variable credits per endpoint&lt;/td&gt;
&lt;td&gt;"Premium" calls drain the wallet unpredictably&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flat, one request equals one unit&lt;/td&gt;
&lt;td&gt;One sub, all domains&lt;/td&gt;
&lt;td&gt;The bill is a function of call count, full stop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Entry pricing was around 39 dollars a month for the whole bundle when I signed up, which for a dozen-ish domains is roughly 3 dollars per domain. Stitching the same coverage from single-domain vendors quoted me well past 100 a month. And because these calculations are deterministic, identical input gives identical output forever, so I cache them. The bill flattens as users grow instead of scaling linearly.&lt;/p&gt;

&lt;p&gt;One thing I did not expect to matter, and then did: the platform keeps shipping new domains, and my flat plan picks them up automatically. Human design and forecast both landed after I signed up, and I did not buy anything or change tiers to use them. New endpoints even show up in the SDKs on their own, because the SDKs are generated from the same spec. With the per-product vendors, every new system is another subscription to evaluate and approve. Here the breadth grows under the same key, so the "dozen domains" number is a floor, not a ceiling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I could not find anywhere else
&lt;/h2&gt;

&lt;p&gt;After evaluating a stack of providers, a few things were genuinely unique to one of them, and they are the reason I stopped shopping. I am listing the ones I verified myself, not the ones on a marketing page.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Why it changed my decision&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One key across a dozen domains, including I Ching, dream symbols, crystals, and angel numbers&lt;/td&gt;
&lt;td&gt;No other single API I tried covered the long-tail domains. Everyone else was astrology-only or astrology plus one extra.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosted MCP for every domain, no local server&lt;/td&gt;
&lt;td&gt;Other providers either had no MCP or shipped a local stdio wrapper you run yourself.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A public, runnable accuracy benchmark against NASA JPL Horizons&lt;/td&gt;
&lt;td&gt;I could re-run it. Nobody else published numbers I could reproduce.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;An engine with no AGPL or copyleft obligation&lt;/td&gt;
&lt;td&gt;The providers that name their stack name AGPL or GPL libraries, which is a license footgun for a closed-source product.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A live OpenAPI spec per domain that matches production&lt;/td&gt;
&lt;td&gt;Several competitors only had a Postman export or a hand-rolled spec, which drifts from the real API.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;New domains ship into the same flat plan&lt;/td&gt;
&lt;td&gt;Existing subscribers get new systems automatically. The per-product vendors charge again for each one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent-first design (MCP, AGENTS.md, llms.txt as one surface)&lt;/td&gt;
&lt;td&gt;Most providers retrofitted AI tooling onto an older REST product, and it shows.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest version: a couple of these exist in isolation somewhere. The combination, in one key, did not exist anywhere else I looked.&lt;/p&gt;

&lt;h2&gt;
  
  
  What real calls look like
&lt;/h2&gt;

&lt;p&gt;For any chart endpoint, geocode the city first. Every coordinate-dependent call needs latitude, longitude, and a timezone, and the location search hands you all three:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://roxyapi.com/api/v2/location/search?q=Berlin&amp;amp;limit=1"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$ROXY_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"offset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"city"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Berlin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"province"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Berlin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"country"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Germany"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"latitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;52.52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;13.405&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Europe/Berlin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"utcOffset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now feed those into the natal chart endpoint. Prefer the IANA timezone string over a decimal offset, since it handles historical DST correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://roxyapi.com/api/v2/astrology/natal-chart &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$ROXY_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "date": "1990-07-15",
    "time": "14:30:00",
    "latitude": 52.52,
    "longitude": 13.405,
    "timezone": "Europe/Berlin",
    "houseSystem": "placidus"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response comes back with &lt;code&gt;ascendant&lt;/code&gt;, &lt;code&gt;planets&lt;/code&gt;, &lt;code&gt;houses&lt;/code&gt;, &lt;code&gt;aspects&lt;/code&gt;, &lt;code&gt;midheaven&lt;/code&gt;, and a &lt;code&gt;summary&lt;/code&gt;, each as structured data. If you would rather use the typed SDK, the method names are generated from the spec, so the same call in TypeScript reads cleanly and returns a &lt;code&gt;{ data, error }&lt;/code&gt; pair:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRoxy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@roxyapi/sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;roxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createRoxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ROXY_API_KEY&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;roxy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;astrology&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateNatalChart&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1990-07-15&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;14:30:00&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;52.52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;13.405&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Europe/Berlin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;ascendant&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;planets&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Numerology is the same pattern with a smaller body. The Life Path endpoint takes three integers and returns the number plus a step-by-step &lt;code&gt;calculation&lt;/code&gt; string, which I appreciated because I could show users how the number was derived:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://roxyapi.com/api/v2/numerology/life-path &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$ROXY_API_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{ "year": 1990, "month": 7, "day": 15 }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You are not stuck with curl either. There are typed SDKs for TypeScript (&lt;code&gt;@roxyapi/sdk&lt;/code&gt;), Python (&lt;code&gt;roxy-sdk&lt;/code&gt;), and PHP (&lt;code&gt;roxyapi/sdk&lt;/code&gt;), and their method names are generated from the spec, so autocomplete just works (&lt;code&gt;generateNatalChart&lt;/code&gt;, &lt;code&gt;calculateLifePath&lt;/code&gt;, &lt;code&gt;getDailyHoroscope&lt;/code&gt;). If you live in WordPress, there is an official plugin on &lt;a href="https://wordpress.org/plugins/roxyapi/" rel="noopener noreferrer"&gt;wordpress.org&lt;/a&gt; with shortcodes and Gutenberg blocks, so a non-developer can drop a horoscope on a page without writing code. Every domain also has its own guide with copy-paste examples, plus integration docs for the no-code tools, which meant I rarely had to guess at a field name. Methodology and verification details live at &lt;a href="https://roxyapi.com/methodology" rel="noopener noreferrer"&gt;roxyapi.com/methodology&lt;/a&gt; if you want the accuracy story before wiring anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you should build it yourself instead
&lt;/h2&gt;

&lt;p&gt;Buying is not always right, and pretending otherwise would make this whole post less useful. If you need exactly one domain, you have in-house astro or astronomy expertise, and you are comfortable either buying the Swiss Ephemeris commercial license or open-sourcing your app, then self-hosting a single-purpose engine is a perfectly good call. A dedicated KP-only or matrimonial-only build can also out-depth any general bundle on its one niche, because depth in a single tradition is its own moat.&lt;/p&gt;

&lt;p&gt;For a multi-system app where I needed verified math across five domains, an engine that is not AGPL, and agents that call tools over MCP, the tradeoff was not close. The 70 percent you can rough out in a weekend is the easy part. The hard 30 percent, which is cross-domain schema consistency, gold-standard verification, clean licensing, remote MCP, SDKs, and localization, is months of work each, and it is the part a serious user notices first when it is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;How do I choose an astrology API for an AI app?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Evaluate the calculation engine and its license first, then the response shape, then how it exposes tools to agents, then pricing. For an AI app specifically, look for structured JSON you can ground your own model on, a hosted MCP server so you are not running a local process, and an engine with no AGPL obligation. RoxyAPI covers all four with a dozen domains under one key, a remote MCP server per domain, and a public accuracy benchmark.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do most astrology APIs use Swiss Ephemeris, and does it matter?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many established providers wrap Swiss Ephemeris, a 1990s desktop C library that is AGPL-3.0 unless you buy a commercial license. For a closed-source SaaS the AGPL network-use clause can obligate you to open-source your whole app. It matters a lot. RoxyAPI runs its own engine, Roxy Ephemeris, verified against NASA JPL Horizons, with no AGPL strings, so you avoid the licensing question entirely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it cheaper to use one multi-domain API or several single-domain ones?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For more than one or two domains, one flat multi-domain subscription is almost always cheaper and simpler than stitching single-domain vendors together. RoxyAPI bundles roughly a dozen domains for about 39 dollars a month at entry, near 3 dollars per domain, against well over 100 a month to assemble the same coverage separately. Deterministic results are also cacheable, so the bill flattens as usage grows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How can I verify an astrology API is actually accurate?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ask for published numbers you can reproduce, not a "NASA-grade" slogan. RoxyAPI publishes an open, MIT-licensed benchmark of 210 planet positions across 21 charts against NASA JPL Horizons, with a median difference around 16 arc-seconds. You can clone it and re-run it. If a provider cannot point you to reproducible numbers, treat the accuracy claim as marketing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I pay more when RoxyAPI adds new domains or features?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No. One flat subscription covers every current domain and every one added later, with no separate purchase. Human design and forecast both shipped as included domains after launch. New endpoints also appear in the typed SDKs automatically, because the SDKs are generated from the same OpenAPI spec, so your client picks them up on the next update.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does MCP support mean for an astrology API?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;MCP, the Model Context Protocol, lets an AI agent call the API as a set of tools. The useful version is a remote (hosted) server you point your agent at with one command, rather than a local server you run and maintain. RoxyAPI ships a hosted MCP server per domain over Streamable HTTP, so Claude Code, Cursor, or your own agent can ground answers in verified calculations while answering in your own voice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The decision that mattered was not which provider had the longest feature list. It was the engine, its license, the response shape, and whether the thing was built for agents or retrofitted for them. RoxyAPI was the one that checked every box without an AGPL asterisk, and it let me spend my real time on the product instead of the math. Start at &lt;a href="https://roxyapi.com/api-reference" rel="noopener noreferrer"&gt;roxyapi.com/api-reference&lt;/a&gt; with the pre-filled test key and run a couple of calls before you decide anything.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>ai</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
