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 several providers with automatic fallback, 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 750+ 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.
Compact responses: fields and precision
Section titled “Compact responses: fields and precision”An agent pays for every token it reads. POST /v1/chart answers with about 32 KB where a model usually wants two columns of it, and every longitude carries fourteen decimals of a number nobody plots to better than a minute of arc.
Two query parameters cut that at the last step, which is why they work on every endpoint:
fieldsis a comma-separated list of paths, relative todata. A path applies to every element of an array, soplanets.namemeans the name of each planet rather than a key on the array.precisionis a whole number from 0 to 15 and rounds every non-integer in the response. Integers are left alone: an id is not a measurement.
curl -X POST "https://api.astroway.info/v1/chart?fields=planets.name,planets.longitude&precision=2" \ -H "X-Api-Key: aw_live_..." -H "Content-Type: application/json" \ -d '{"date":"1990-05-15","time":"14:30:00","timezone":"Europe/Kyiv","latitude":50.45,"longitude":30.52}'On a natal chart that pair returns 511 bytes instead of 32,410, a response sixty times smaller.
A path that matched nothing does not disappear quietly. It comes back in _fields_unmatched beside the answer, because some fields are genuinely optional and absent from a given response, which is not an error. A list where nothing matched is a typo, and that returns 400 FIELDS_UNKNOWN naming the paths. An empty object in place of a chart would be a confident wrong answer.
Both are query parameters rather than headers, deliberately: the response cache keys on method, path, body, the sorted query and the plan. A mode carried in a header would repeat the 2026-08-22 incident, where Accept-Language was outside the key and one caller’s German answer was served to the next as Ukrainian.
Shaping applies to data only. The envelope, _footer and _warning stay whole.
The same works over MCP. Every tool on mcp.astroway.info takes fields and precision alongside its own parameters and passes them to the API on the same query string. A natal chart over MCP weighed 91,737 bytes because the data arrived twice: once as an indented text block and once as structured output. With the same three paths and precision: 2 it is 1,182.
| Natal chart call | Bytes |
|---|---|
| MCP, as it stood before 2026-09-21 | 91,737 |
| MCP, text block no longer indented | 72,484 |
MCP, precision: 2 | 61,356 |
MCP, three paths in fields plus precision: 2 | 1,182 |
REST, three paths in fields plus precision=2 | 559 |
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