URL conventions and free endpoints
If you’re seeing 404s on what should be a routine request - it’s almost always a wrong prefix or you’re looking for Swagger UI at a non-standard path. This page documents every canonical URL and alias.
Path prefix
Section titled “Path prefix”All endpoints live under /v1/. No other prefix is accepted.
✅ https://api.astroway.info/v1/chart✅ https://api.astroway.info/v1/health❌ https://api.astroway.info/api/chart - 404 (missing /v1/ prefix)❌ https://api.astroway.info/api/v1/chart - 404 (our internal path, not exposed)❌ https://api.astroway.info/v1/v1/chart - 308 redirect to /v1/chart (typical SDK base-URL bug fix)If you accidentally hit /api/<endpoint>, we return 404 with error.code = "WRONG_PREFIX" and a hint - your SDK can intercept this and surface the correct path.
Not an LLM proxy
Section titled “Not an LLM proxy”AstroWay is an astrology calculations API, not an LLM proxy. If you arrive with OpenAI SDK muscle memory and try to just swap base_url to ours - it won’t work:
❌ POST /v1/chat/completions - doesn't exist❌ POST /v1/completions - doesn't exist❌ GET /v1/models - doesn't exist (deliberately)❌ POST /v1/embeddings - doesn't existWe don’t publish the AI provider list for two reasons:
- Not actionable for integrators. You call
/v1/interpret/natalor/v1/horoscope/daily- our server picks an LLM from a rotated fallback chain (Gemini → Groq → Cerebras → SambaNova → Mistral → …) on its own. Swapping providers doesn’t change the response shape, only latency and occasional rewording. - Operational freedom. The chain changes without warning (we removed HuggingFace 2026-05-18, added NIM/ZAI). If we declared a fixed
models[]it would become a de-facto contract.
AI is present purely as an implementation detail in /v1/interpret/* (natal, synastry, transits), /v1/horoscope/* (daily/weekly forecasts), /v1/dev-assistant (docs helper) and /v1/reports/* (PDF narratives). The remaining 400+ endpoints are deterministic Swiss Ephemeris, no AI involved.
OpenAPI 3.1 spec
Section titled “OpenAPI 3.1 spec”The canonical URL is /v1/openapi.json. For integration convenience we accept these aliases:
| Alias | Where | Why |
|---|---|---|
/v1/swagger.json | 301 → /v1/openapi.json | Swagger 2.0 / openapi-generator default |
/v1/api-docs/swagger.json | 301 → /v1/openapi.json | springdoc default |
/v1/v3/api-docs | 301 → /v1/openapi.json | Spring Boot 3 |
/v1/v2/api-docs | 301 → /v1/openapi.json | Spring Boot 2 |
/v1/swagger/v1/swagger.json | 301 → /v1/openapi.json | ASP.NET Core |
/v1/swagger, /v1/swagger-ui, /v1/swagger-ui.html | 302 → /v1/docs | Swagger UI host |
Interactive Swagger UI lives at /v1/docs (also mirrored in the browseable docs at /docs/api/).
Free system endpoints
Section titled “Free system endpoints”These paths require no API key and don’t burn credits - call them from any frontend, monitor, or health-check.
| Path | Returns |
|---|---|
GET /v1/health | {status, version, uptime_seconds, timestamp} - fast liveness probe |
GET /v1/health/deep | Per-DB reachability check (for UptimeRobot / Pingdom), 503 if any DB is down |
GET /v1/version | {version, build_commit, started_at, uptime_seconds, docs_url} - SDK self-check |
GET /v1/openapi.json | Full OpenAPI 3.1 spec with examples + x-codeSamples |
GET /v1/docs | Interactive Swagger UI |
GET /v1/reference/* | Canonical reference data (signs, planets, houses, aspects) - IP-rate-limited, no key |
GET /v1/i18n/langs, GET /v1/i18n/dict/:lang | Language list + UI dictionaries - for widgets |
Free endpoints that require an account
Section titled “Free endpoints that require an account”Account endpoints accept a JWT (issued at login) but don’t deduct credits from your plan balance.
| Prefix | What |
|---|---|
/v1/auth/* | Register, login, refresh, password reset |
/v1/me, /v1/me/* | Profile, usage stats, recent calls |
/v1/keys, /v1/keys/* | API key management |
/v1/admin/* | Admin panel (requires api_role = admin) |
/v1/messages | Feedback widget submissions (anonymous, rate-limited) |
/v1/dev-assistant, /v1/dev-assistant/stream | AI docs assistant (anonymous, separate quota) |
/v1/widget-config | Runtime feature-flags for the widget |
/v1/embed/* | Embeddable iframe widgets (IP-rate-limited) |
/v1/oauth/* | Google / GitHub login |
/v1/books/* | Birth Book - separate scoped JWT (issued by WordPress mu-plugin) |
Paid calculation endpoints
Section titled “Paid calculation endpoints”All astrology calculations deduct credits from your plan. Exact per-endpoint cost is on Per-endpoint Cost. Summary:
| Tier | Credits | Examples |
|---|---|---|
| 0 | 0 | /v1/reference/* - public lookups, no key |
| ½ | 5 | /v1/kabbalah/sephiroth, /v1/fixed-stars/catalog - static tables |
| 1 | 10 | /v1/planets, /v1/sun-times, /v1/moon-phase |
| 2 | 20 | /v1/chart, /v1/harmonics, /v1/horary |
| 3 | 50 | /v1/synastry, /v1/progressions, /v1/acg |
| 4 | 100 | /v1/transit-calendar, /v1/group-synastry |
| 5 | 250 | /v1/rectification/trutine, AI narratives /v1/reports/ai/* |
| 6 | 500 | /v1/rectification |
| 7 | 5000 | PDF reports: /v1/reports/natal, /v1/reports/synastry |
Cache discounts: X-Cache: HIT (5-min idempotency cache) - 0 credits. AI endpoints (/interpret/*) - 50% off on cache hit.
Response headers include X-Credits-Used, X-Credits-Remaining, X-Credits-Limit - your SDK can track balance automatically.
Response language
Section titled “Response language”Language comes from ?lang= or the Accept-Language header. Twenty-one languages are supported.
Fields from closed sets (signs, planets, weekdays, moon phases, the Human Design vocabulary, the Major Arcana card names) are returned twice: the English identifier stays where it was, as a stable contract, and a localized object carries the translation alongside it:
{ "phaseName": "Waxing Gibbous", "moonSign": "Capricorn", "localized": { "phaseName": "Zunehmender Mond", "moonSign": "Steinbock", "sunSign": "Löwe" }}Match on the English values, display localized. These names come from an explicit terminology table in code rather than from machine translation: terminology cannot be translated word-for-word, or a moon phase turns into a finance term.
In Human Design, localized.centers keys by the same identifiers as centers[].name, so you match on the key and never on a translated string. Tarot does the same through localized.cards, keyed by the card slug.
Fields from open sets are still English and documented as such: themes[] on planet of the day, tarot keywords and card meanings, Human Design channel and gate names. Minor Arcana names are composed from frozen rank and suit forms, so the whole 78-card deck is localised and nameLocalized is true for it; the field stays false for anything the table cannot name. Translate the open sets on your side, or wait for dedicated dictionaries.
Unknown birth time
Section titled “Unknown birth time”A large share of end users do not know their exact birth time. Filling in an assumed noon and computing the chart as usual returns an ascendant and house cusps that look authoritative and are wrong by roughly a sign for every two hours of error.
So instead of time you can send timeUnknown: true:
curl -X POST https://api.astroway.info/v1/chart \ -H "X-Api-Key: aw_live_..." \ -H "Content-Type: application/json" \ -d '{"date":"1990-03-15","timeUnknown":true,"timezoneOffset":2,"latitude":50.45,"longitude":30.52}'What the API returns in this mode:
| Field | Value |
|---|---|
planets | computed for local noon |
aspects | computed, but aspects involving the Moon are unreliable (see below) |
houses | null |
houseAspects | null |
chartSect | null |
siderealTime | null |
timeUnknown | a block stating what was omitted and where the Moon can be |
timeUnknown.moon gives the lunar longitude at the start and end of the day, the arc covered (11.8-15.4°), every sign the Moon occupied, and a changesSign flag. The Moon can change sign within a single day, so this range is worth surfacing to the user rather than hiding behind a single number.
/v1/chart and /v1/synastry accept the flag (in synastry it can be set per partner). Every other endpoint depends on houses or angles and answers 400 TIME_UNKNOWN_UNSUPPORTED: an explicit error beats a silently ignored flag and an invented ascendant in the response.
API versioning
Section titled “API versioning”Major version in the URL (/v1/). Breaking changes only ship as /v2/, never in-place. Details - Versioning.
GET /v1/version returns the current deploy:
curl https://api.astroway.info/v1/version# {# "version": "2.71.0",# "build_commit": "abc1234",# "started_at": "2026-05-27T10:42:00Z",# "uptime_seconds": 12345,# "docs_url": "https://api.astroway.info/docs/api/"# }SDKs can call this on boot for debugging “why is the behaviour suddenly different” - the build_commit field uniquely identifies the deploy.
Most-common integrator mistakes
Section titled “Most-common integrator mistakes”From 90 days of prod logs, the top 5 integration mistakes we see:
/api/<endpoint>instead of/v1/<endpoint>: WordPress wp-json muscle memory. We now return JSON 404 with a hint./v1/v1/<endpoint>: SDK configured withbaseUrl = .../v1and the call adds/v1/Xon top. Now 308 redirected./swagger.jsoninstead of/v1/openapi.json: Postman/Insomnia auto-probe. Now 301 redirected./v1/me,/v1/auth/keys/mewithoutAuthorization: Bearer <jwt>→ 401. Check that the token reaches the header./v1/embed/natalwith an API key instead of an embed token → 401. Embed endpoints accept the public embed token from the dashboard, not your API key.
Related
Section titled “Related”- Quick Start: first request in 5 minutes
- Authentication: JWT, API keys, embed tokens
- Per-endpoint Cost: exact credit table
- Errors: error code reference
- OpenAPI 3.1: full spec