:::note
**TL;DR**
- One Python SDK covers Western astrology, Vedic kundli, panchang, tarot, numerology, biorhythm, I Ching, crystals, dreams, and angel numbers.
- Install with `pip install roxy-sdk` or `uv add roxy-sdk`. Every method has a matching `_async` variant for FastAPI and asyncio.
- Geocode the birth city with `roxy.location.search_cities` first. Pass the returned `utcOffset` (a decimal) as the `timezone` kwarg on chart endpoints.
- Build a daily horoscope, a Vedic birth chart, and a tarot spread in under 30 minutes with [`roxy-sdk`](https://pypi.org/project/roxy-sdk/).
:::

**About the author:** Brett Calloway is a Developer Advocate and AI Integration Specialist with 12 years of experience building APIs and developer tooling. He has led developer relations at two Series B SaaS companies and spoken at PyCon and JSConf on building context-rich AI agents using Model Context Protocol.

## Why Python developers stall when building insight apps

Every astrology, tarot, or numerology side project starts with the same blocker: the math. Natal chart calculations need an ephemeris verified against authoritative sources. Vedic panchang needs sunrise and tithi math. KP needs sub-lord theory. Tarot needs a seeded RNG that produces the same card per user per day. Most Python developers spend a weekend piecing together single-domain libraries, a tarot deck JSON, and three timezone workarounds, then ship a broken V1. The problem is not Python, the problem is stitching four unrelated sources together and reinventing every interpretation string yourself. The [Python RoxyAPI SDK](/products/astrology-api "production-ready Python SDK for astrology, Vedic, tarot, and more") collapses twelve spiritual-data domains behind one import. Calculations are verified against NASA JPL Horizons and cross-referenced against authoritative panchang sources. This post walks through the three patterns that matter: geocode, compute, interpret.

## Install and authenticate in two minutes

Pick your package manager. The SDK ships wheels for Python 3.10 and up and depends only on `httpx`, so it installs in seconds on any platform.

```bash
# pip
pip install roxy-sdk

# or uv (faster, recommended)
uv add roxy-sdk
```

Get an API key at [pricing](/pricing "RoxyAPI pricing and plan tiers"). Starter is $39 a month for 25,000 requests across every domain with one key. Every endpoint uses the same `X-API-Key` header, and `create_roxy` injects it automatically.

```python
import os
from roxy_sdk import create_roxy

roxy = create_roxy(os.environ["ROXY_API_KEY"])

# A first call. No boilerplate, no JSON parsing.
horoscope = roxy.astrology.get_daily_horoscope(sign="aries")
print(horoscope["overview"])
```

The SDK sends `X-SDK-Client: roxy-sdk-python/0.1.3` on every request so you can track SDK usage in your own logs alongside the API response.

## Geocode the birth city before any chart call

Chart endpoints (natal, Vedic, synastry, panchang, KP, transits, compatibility) need `latitude`, `longitude`, and `timezone` as a **decimal number**. Never ask the user to type coordinates. Geocode the city first, then pass the result into the chart method.

The location response is a paginated envelope. Each city has two timezone fields: `timezone` is the IANA identifier (`Asia/Kolkata`) for your date library, and `utcOffset` is the DST-adjusted decimal (`5.5` for IST, `-5` for EST, `9` for JST). Pass `utcOffset` to chart endpoints.

```python
result = roxy.location.search_cities(q="Mumbai, India")
city = result["cities"][0]
# city = { "city": "Mumbai", "latitude": 19.017, "longitude": 72.857,
#          "timezone": "Asia/Kolkata", "utcOffset": 5.5, ... }

natal = roxy.astrology.generate_natal_chart(
    date="1990-01-15",
    time="14:30:00",
    latitude=city["latitude"],
    longitude=city["longitude"],
    timezone=city["utcOffset"],
)
```

Passing the IANA string instead of the decimal returns `validation_error: expected number, received string`. This is the single most common mistake and the SDK AGENTS.md flags it in red for AI coding agents.

## Build a daily horoscope in three lines

The daily horoscope endpoint is the cheapest hook in any consumer app. It drives daily active users, push notifications, and streaks. One GET call, no body, no coordinates.

```python
h = roxy.astrology.get_daily_horoscope(sign="scorpio")

# h is a plain dict, ready to serialize to your app
print(h["overview"])        # General daily forecast, ~230 chars
print(h["love"])             # Love and relationship forecast
print(h["career"])           # Career outlook
print(h["luckyNumber"])      # Integer
print(h["luckyColor"])       # String
print(h["compatibleSigns"])  # List of compatible signs for today
print(h["moonPhase"])        # Current moon phase
print(h["energyRating"])     # 1 to 10
```

Weekly (`get_weekly_horoscope`) and monthly (`get_monthly_horoscope`) variants return the same shape plus `luckyDays[]` and `keyDates[]`. For caching, the horoscope changes at midnight UTC, so a 1-day TTL works. See the [Western astrology reference](/api-reference#tag/western-astrology "full Western astrology endpoint reference with request and response schemas") for every field.

Ready to ship? [Get your API key](/pricing "RoxyAPI pricing, get your key in 60 seconds") and drop a daily-horoscope card into your app this afternoon.

## Generate a Vedic kundli and panchang

Vedic is where the depth shows. The kundli endpoint returns all twelve rashi houses with the planets placed in each, a `meta` dict keyed by planet name, house interpretations, and combustion / planetary-war metadata.

```python
kundli = roxy.vedic_astrology.generate_birth_chart(
    date="1990-01-15", time="14:30:00",
    latitude=28.6139, longitude=77.209, timezone=5.5,
)

# Iterate rashis for the 12-sign chart view
for rashi in ["aries", "taurus", "gemini", "cancer", "leo", "virgo",
              "libra", "scorpio", "sagittarius", "capricorn", "aquarius", "pisces"]:
    signs = kundli[rashi]["signs"]  # planets in this rashi (can be empty)
    if signs:
        print(rashi, [p["graha"] for p in signs])

# Or look up any planet directly
print(kundli["meta"]["Sun"]["rashi"])       # e.g. "Capricorn"
print(kundli["meta"]["Moon"]["nakshatra"]["name"])  # e.g. "Uttara Ashadha"
```

Panchang is the other India-market staple. One call returns tithi, nakshatra, yoga, karana, rahu kaal, abhijit muhurta, gulika, and brahma muhurta for any date and location. See the [Vedic astrology API](/products/vedic-astrology-api "production-ready Vedic astrology API with kundli, panchang, dashas, and KP") product page for the full endpoint list.

## Cast a tarot spread with a deterministic seed

Tarot readings need a seeded RNG so the same user gets the same card on the same day. The SDK handles the seed automatically. Pass a user ID or session ID and the card is stable until tomorrow.

```python
# Daily card, same answer for this user until midnight
card = roxy.tarot.get_daily_card(seed="user-42")
print(card["card"]["name"])          # e.g. "The Star"
print(card["card"]["imageUrl"])      # Full-res card art URL
print(card["card"]["reversed"])      # bool, reversed meaning applies
print(card["dailyMessage"])           # Contextual message for today

# Ten-position Celtic Cross for a specific question
reading = roxy.tarot.cast_celtic_cross(
    question="What should I focus on this quarter?",
    seed="user-42-q1",
)
for p in reading["positions"]:
    print(p["position"], p["name"], p["card"]["name"], "-", p["interpretation"])

# Lightweight yes / no for an impulse feature
answer = roxy.tarot.cast_yes_no(question="Should I take the offer?")
print(answer["answer"])     # "Yes" | "No" | "Maybe"
print(answer["strength"])    # "Strong" | "Qualified"
print(answer["card"]["name"])
```

Card art ships as public HTTPS URLs, no signed-URL dance, safe to embed directly. The full [tarot API reference](/api-reference#tag/tarot "full tarot API reference with spread schemas and card catalog") has 10 endpoints including three-card, love spread, and the 78-card catalog.

## Handle errors and rate limits without noise

The SDK raises `RoxyAPIError` on any non-2xx response. The `code` field is machine-readable and stable, safe to switch on. The `error` field is human-readable and may change wording between releases.

```python
from roxy_sdk import create_roxy, RoxyAPIError

try:
    result = roxy.astrology.get_daily_horoscope(sign="scorpio")
except RoxyAPIError as e:
    if e.code == "validation_error":
        # e.error includes the failing field
        print(f"Bad input: {e.error}")
    elif e.code == "rate_limit_exceeded":
        # Monthly quota hit. Upgrade or wait until the 1st.
        print("Quota reached, retry next month")
    elif e.code in ("subscription_inactive", "invalid_api_key"):
        print(f"Auth problem: {e.code}")
    else:
        print(f"API error {e.status_code}: {e.code} - {e.error}")
```

Validation errors include an `issues[]` array with the specific field path that failed. No more guessing which parameter your integration sent wrong. The canonical error code list lives on the [SDK docs page](/docs/sdk "RoxyAPI Python and TypeScript SDK docs, quickstart, and reference").

## Deploy behind FastAPI with async

Every sync method has an `_async` variant for asyncio. Drop the SDK into FastAPI with zero glue code.

```python
from fastapi import FastAPI
from roxy_sdk import create_roxy, RoxyAPIError
import os

app = FastAPI()
roxy = create_roxy(os.environ["ROXY_API_KEY"])

@app.get("/horoscope/{sign}")
async def horoscope(sign: str):
    try:
        return await roxy.astrology.get_daily_horoscope_async(sign=sign)
    except RoxyAPIError as e:
        return {"error": e.code, "message": e.error}, e.status_code

@app.post("/natal")
async def natal(city: str, date: str, time: str):
    locations = await roxy.location.search_cities_async(q=city)
    c = locations["cities"][0]
    return await roxy.astrology.generate_natal_chart_async(
        date=date, time=time,
        latitude=c["latitude"], longitude=c["longitude"],
        timezone=c["utcOffset"],
    )
```

The SDK shares one `httpx.AsyncClient` across calls, so connection pooling is automatic. Benchmarks on a Hetzner CX23 show median end-to-end latency of 87 ms for the horoscope endpoint and 180 ms for a full natal chart, including the round trip from Frankfurt to the API.

## Give your AI agents the same SDK your code uses

Python insight apps are a natural fit for Claude Code, Cursor, Windsurf, and the OpenAI Agents SDK because the data they return is structured JSON, not prose. The Roxy Python SDK ships an `AGENTS.md` inside the wheel at `site-packages/roxy_sdk/AGENTS.md`, so any AI coding agent that reads `AGENTS.md` (Claude Code, Cursor, Windsurf, Copilot, Codex, Gemini CLI) picks up the critical patterns automatically: geocode first, timezone is a decimal, location returns an envelope, method names come from `operationId`, not URL paths. Separately, each domain exposes a remote MCP server at `https://roxyapi.com/mcp/{domain}` (Streamable HTTP, no stdio, no self-hosting) so agents can call the API directly without glue code. Read the [MCP setup guide](/docs/mcp "RoxyAPI MCP server setup, authentication, and tool naming") to wire up Claude Desktop or Cursor in under five minutes.

## Troubleshooting checklist for the first hour

These are the five issues every new integrator hits. Work through them once and you will not hit them again.

First, the `timezone` parameter must be a decimal number. `Asia/Kolkata` fails. Use `city["utcOffset"]`, not `city["timezone"]`. Second, `time` must include seconds in `HH:MM:SS` format, `14:30` fails. Third, numerology takes `year`, `month`, `day` as integers, not a single date string. Fourth, location responses are paginated envelopes, always index `result["cities"][0]`, never `result[0]`. Fifth, do not wrap the API key in quotes inside the env file, export it raw. If you hit `invalid_api_key` with a correct key, check for a trailing newline or stray whitespace. The SDK strips neither, deliberately, so upstream errors surface quickly.

## Frequently Asked Questions

**Q: What does the Roxy Python SDK cover?**
A: Twelve spiritual-data domains behind one key. Western astrology, Vedic or Jyotish, numerology, tarot, biorhythm, I Ching, crystals, dreams, angel numbers, and location. Plus usage stats. A single `pip install roxy-sdk` gets every domain, no stitching needed.

**Q: Is the SDK typed and does it support async?**
A: Every method is a typed Python function with keyword arguments. Every sync method has a matching `_async` variant that uses `httpx.AsyncClient` for FastAPI, asyncio, and any event loop. Response shapes follow the OpenAPI spec exactly.

**Q: Why does the timezone field on a city return Asia Kolkata instead of a number?**
A: The city `timezone` field is the IANA identifier for your date library. Chart endpoints need a decimal like 5.5. Use the `utcOffset` field on the same city (already DST-adjusted) and pass it as the `timezone` kwarg.

**Q: Can I use the SDK from a Jupyter notebook or a CLI script?**
A: Yes. The SDK has no framework dependency. Works in notebooks, scripts, FastAPI, Flask, Django, Celery workers, and background jobs. Shared `httpx` client is safe across threads and asyncio tasks.

**Q: How do I avoid hitting the monthly quota during development?**
A: Keep the API key out of browser-exposed code and cache deterministic responses locally. Daily horoscopes, tarot daily cards, and angel number lookups are stable within a day, so a 1-day cache is safe. Use the free `get_usage_stats` call to monitor quota.

## Conclusion

Python developers stall on insight apps because the math and the content are both hard. The Roxy Python SDK removes both layers: one import, twelve domains, typed responses, async by default. Start with the [Python SDK quickstart](/docs/sdk "RoxyAPI Python and TypeScript SDK docs, quickstart, and reference"), then pick the domain your users want first. [Pricing](/pricing "RoxyAPI pricing and plan tiers") starts at $39 a month for 25,000 requests across every endpoint, so your V1 costs less than a domain name.