Metadata-Version: 2.5
Name: astro96-api
Version: 0.1.0
Summary: Official Python client for the 96Astro API: Vedic and Western astrology, panchang, matching, reports and AI readings.
Project-URL: Documentation, https://96astroapi.com/docs/sdks
Project-URL: API reference, https://96astroapi.com/docs/reference
Author-email: "Kuber Astro (96Astro)" <support@96astroapi.com>
License-Expression: MIT
License-File: LICENSE
Keywords: 96astro,api,astrology,horoscope,jyotish,kundli,panchang,sdk,vedic
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx<1,>=0.23
Requires-Dist: typing-extensions>=4.7
Provides-Extra: test
Requires-Dist: pytest>=7; extra == 'test'
Description-Content-Type: text/markdown

# astro96-api

The official Python client for the [96Astro API](https://96astroapi.com): Vedic and Western astrology, panchang, matching, reports and AI readings over one JSON API.

- Sync (`Astro96Client`) and asyncio (`AsyncAstro96Client`) clients on `httpx`, Python 3.9+
- A typed method for every capability, generated from the OpenAPI document (`TypedDict` request bodies)
- Retries with backoff on `429` / `503` (honouring `Retry-After`), automatic idempotency keys so a retry is never charged twice
- `ApiError` with `code`, `field`, `retryable` and `request_id`; rate-limit headers on every response
- PDF job helpers (`create_pdf_job`, `wait_for_job`, `download_file`, `render_pdf`) and webhook signature verification

> **Not on PyPI yet.** Publishing is in progress; until then `pip install astro96-api` will not find the package. Contact support for a pre-release wheel, or build it from this repository (see Development).

## Install

```bash
pip install astro96-api
```

## Quickstart

```python
from astro96_api import Astro96Client

astro = Astro96Client()  # reads ASTRO_API_KEY (sk_test_... or sk_live_...)

res = astro.vedic_charts.planet_positions(
    {"datetime": "1990-10-18T14:30:00", "timezone": "Asia/Kolkata", "latitude": 28.6139, "longitude": 77.209}
)
print(res.data["ascendant"]["sign"], res.meta["credits_used"], res.rate_limit)
```

```python
import asyncio
from astro96_api import AsyncAstro96Client

async def main() -> None:
    async with AsyncAstro96Client() as astro:
        res = await astro.panchang.daily_panchang(
            {"date": "2026-10-15", "timezone": "Asia/Kolkata", "latitude": 28.6139, "longitude": 77.209}
        )
        print(res.data)

asyncio.run(main())
```

`POST /v1/{family}/{capability}` is `client.family.capability()` in snake_case. Each call returns an `ApiResponse` with `data`, `meta`, `status`, `request_id`, `rate_limit`, `replayed`, `idempotency_key` and `headers`.

Response `data` is typed as a `TypedDict` of its top-level fields, **from each endpoint's published schema**: a hand-written contract for the main endpoints, and for almost all others a schema measured from several real answers (a required field was in every answer). A few have only the schema inferred from one example. An answer to an input none of the samples covered can still differ, so check optional fields. Nested objects are plain dicts.

### Errors

```python
from astro96_api import ApiConnectionError, ApiError

try:
    astro.vedic_charts.planet_positions(birth)
except ApiError as e:
    print(e.status, e.code, e.field, e.retryable, e.request_id)
except ApiConnectionError as e:
    print("network", e.timed_out)
```

### Retries and idempotency

`429 rate_limited` and `503` are retried after `Retry-After` (up to `max_retry_delay`, default 30 s). Other `retryable` errors and network failures are retried only when the call has an idempotency key, which every POST gets automatically (`auto_idempotency=True`) and keeps across retries. `quota_exhausted` is never retried. Pass your own key to retry across processes:

```python
from astro96_api import idempotency_key
astro.reports.report_career(birth, idempotency_key=stored_key or idempotency_key())
```

Client options: `api_key`, `base_url`, `auth` (`"bearer"` or `"x-api-key"`), `timeout` (60 s), `max_retries` (2), `max_retry_delay`, `auto_idempotency`, `headers`, `http_client`. Per call: `idempotency_key=`, `timeout=`, `max_retries=`, `headers=`.

### PDF reports

```python
pdf = astro.render_pdf("report-career", birth, branding={"brand_name": "Shree Jyotish Kendra"})
# or step by step: create_pdf_job -> wait_for_job -> download_file
```

### Webhooks

```python
from astro96_api import verify_webhook
event = verify_webhook(raw_body, headers, os.environ["ASTRO_WEBHOOK_SECRET"])
```

Full guide: https://96astroapi.com/docs/sdks

> **Not wrapped yet:** AI streaming (`stream: true` / `Accept: text/event-stream`), the `/v1/conversations` routes and `.ics` calendar export. These clients always ask for JSON; call those routes over plain HTTP (see https://96astroapi.com/docs/ai-streaming-and-chat). Everything else, including the image options (`image_format`, `image_theme`, `image_delivery`), works through the typed inputs.

## Development

The typed methods in `src/astro96_api/_generated/` are written by `tools/sdk/generate_python.py` from `packages/registry/openapi/v1.openapi.json`; never edit them by hand.

```bash
python ../../tools/sdk/generate_python.py   # regenerate after the OpenAPI document changes
python -m pytest                            # unit tests (httpx.MockTransport) + drift test
```

### Publishing

1. Set `name` in `pyproject.toml` if it should differ from `astro96-api`, bump `version` (and `src/astro96_api/_version.py`), and add a `license` and `LICENSE` file.
2. `python ../../tools/sdk/generate_python.py --check && python -m pytest`
3. `python -m build` then `python -m twine upload dist/*` with a PyPI token for the project.

## Audio reports

Any report can come back as a spoken MP3 in 26 languages, as a background job. Poll it as you would a PDF job. See [Audio reports](https://96astroapi.com/docs/audio-reports).

```python
job = client.create_audio_job(
    "report-career",
    {"datetime": "1990-10-18T14:30:00", "timezone": "Asia/Kolkata", "latitude": 28.6139, "longitude": 77.209},
    language="hi",
    minutes=5,
)
```

Palm and face readings are ordinary calls that take a photo as base64: see [Palm and face readings](https://96astroapi.com/docs/photo-readings).
