Skip to content
AstroWay/api v2.158.7 · api-conventions
all systems operational

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.

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.

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 exist

We don’t publish the AI provider list for two reasons:

  1. Not actionable for integrators. You call /v1/interpret/natal or /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.
  2. 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.

The canonical URL is /v1/openapi.json. For integration convenience we accept these aliases:

AliasWhereWhy
/v1/swagger.json301 → /v1/openapi.jsonSwagger 2.0 / openapi-generator default
/v1/api-docs/swagger.json301 → /v1/openapi.jsonspringdoc default
/v1/v3/api-docs301 → /v1/openapi.jsonSpring Boot 3
/v1/v2/api-docs301 → /v1/openapi.jsonSpring Boot 2
/v1/swagger/v1/swagger.json301 → /v1/openapi.jsonASP.NET Core
/v1/swagger, /v1/swagger-ui, /v1/swagger-ui.html302 → /v1/docsSwagger UI host

Interactive Swagger UI lives at /v1/docs (also mirrored in the browseable docs at /docs/api/).

These paths require no API key and don’t burn credits - call them from any frontend, monitor, or health-check.

PathReturns
GET /v1/health{status, version, uptime_seconds, timestamp} - fast liveness probe
GET /v1/health/deepPer-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.jsonFull OpenAPI 3.1 spec with examples + x-codeSamples
GET /v1/docsInteractive 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/:langLanguage list + UI dictionaries - for widgets

Account endpoints accept a JWT (issued at login) but don’t deduct credits from your plan balance.

PrefixWhat
/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/messagesFeedback widget submissions (anonymous, rate-limited)
/v1/dev-assistant, /v1/dev-assistant/streamAI docs assistant (anonymous, separate quota)
/v1/widget-configRuntime 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)

All astrology calculations deduct credits from your plan. Exact per-endpoint cost is on Per-endpoint Cost. Summary:

TierCreditsExamples
00/v1/reference/* - public lookups, no key
½5/v1/kabbalah/sephiroth, /v1/fixed-stars/catalog - static tables
110/v1/planets, /v1/sun-times, /v1/moon-phase
220/v1/chart, /v1/harmonics, /v1/horary
350/v1/synastry, /v1/progressions, /v1/acg
4100/v1/transit-calendar, /v1/group-synastry
5250/v1/rectification/trutine, AI narratives /v1/reports/ai/*
6500/v1/rectification
75000PDF 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.

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.

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:

Terminal window
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:

FieldValue
planetscomputed for local noon
aspectscomputed, but aspects involving the Moon are unreliable (see below)
housesnull
houseAspectsnull
chartSectnull
siderealTimenull
timeUnknowna 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.

Major version in the URL (/v1/). Breaking changes only ship as /v2/, never in-place. Details - Versioning.

GET /v1/version returns the current deploy:

Terminal window
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.

From 90 days of prod logs, the top 5 integration mistakes we see:

  1. /api/<endpoint> instead of /v1/<endpoint>: WordPress wp-json muscle memory. We now return JSON 404 with a hint.
  2. /v1/v1/<endpoint>: SDK configured with baseUrl = .../v1 and the call adds /v1/X on top. Now 308 redirected.
  3. /swagger.json instead of /v1/openapi.json: Postman/Insomnia auto-probe. Now 301 redirected.
  4. /v1/me, /v1/auth/keys/me without Authorization: Bearer <jwt> → 401. Check that the token reaches the header.
  5. /v1/embed/natal with an API key instead of an embed token → 401. Embed endpoints accept the public embed token from the dashboard, not your API key.
Was this helpful?
Suggest an edit

Last updated: