Errors
Every non-2xx response is an RFC 9457 problem document, served with Content-Type: application/problem+json. Errors are part of the API contract: each failure mode has a stable, namespaced code, and the type URI of every problem — https://docs.astralastrologyapi.com/errors/<code> — resolves to that code’s section on this page.
The problem shape
Section titled “The problem shape”{ "type": "https://docs.astralastrologyapi.com/errors/settings.ayanamsa.required", "title": "ayanamsa is required when zodiac is sidereal", "status": 422, "code": "settings.ayanamsa.required", "detail": "zodiac is sidereal but no ayanamsa was provided", "request_id": "req_8fK2x1"}| Field | Contract |
|---|---|
type |
URI identifying the error; links to the matching section here. Mirrors code. |
title |
Short human-readable summary. Stable per code. |
status |
The HTTP status, repeated in the body. |
code |
The field to branch on. Stable, namespaced, never renamed within v1. |
detail |
Human-readable explanation of this specific occurrence. Human-only — the wording may change at any time; never parse it. |
request_id |
Correlates this response with our server logs. Log it, and include it in any support request. |
Handling guidance in one line: branch on code, log request_id, show or discard detail, and never string-match title or detail in program logic.
Status codes
Section titled “Status codes”| Status | Meaning | Codes |
|---|---|---|
| 400 | The request body could not be parsed against the schema. | request.malformed |
| 401 | Authentication failed. | auth.missing, auth.invalid_key |
| 422 | The request parsed, but the input is semantically invalid. | subject.*, settings.* |
| 500 | A bug on our side. It pages us. | server.internal |
| 503 | The server is shedding load or a calculation timed out. Safe to retry with backoff. | server.overloaded, server.calc_timeout |
Request errors
Section titled “Request errors”request.malformed
Section titled “request.malformed”400 — Request body is malformed.
When you get it: the body is not valid JSON, or it contains fields the schema does not define. The request schema is strict (additionalProperties: false): unknown fields are rejected, not silently ignored, so a typo like datetme fails loudly instead of producing a chart for the wrong moment.
How to fix: validate your JSON and remove any field not in the endpoint’s schema. Note that subject.local_datetime and subject.location.place are reserved for a future release and are rejected as unknown fields today.
curl -s https://astral-external-api-iksoi6t3nq-ue.a.run.app/v1/western/positions \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{not json'Authentication errors
Section titled “Authentication errors”auth.missing
Section titled “auth.missing”401 — Missing bearer token.
When you get it: the request has no Authorization header (or the header is not a bearer scheme).
How to fix: send Authorization: Bearer ak_... on every /v1/* request. See Authentication.
auth.invalid_key
Section titled “auth.invalid_key”401 — Unknown API key.
When you get it: the bearer token is not an active key — mistyped, revoked, or issued for a different environment.
How to fix: check that the deployed key matches what was provisioned, and that you are using the intended ak_test_ / ak_live_ key. If you rotated recently, confirm the old key was not still in use when it was revoked.
Validation errors
Section titled “Validation errors”All validation errors are 422 Unprocessable Content: the request was well-formed, but the input cannot produce a correct calculation.
subject.time.required
Section titled “subject.time.required”422 — Subject time is required.
When you get it: subject.datetime is absent. Every calculation is anchored to a moment, so a subject without one is unanswerable.
How to fix: supply subject.datetime as ISO 8601 with an explicit UTC offset, e.g. "1990-05-15T14:30:00-04:00".
subject.time.invalid_format
Section titled “subject.time.invalid_format”422 — Subject time is not valid ISO 8601.
When you get it: subject.datetime is present but not parseable as an ISO 8601 datetime with a UTC offset.
How to fix: use the full form YYYY-MM-DDTHH:MM:SS±HH:MM (or Z for UTC). Local times without an offset are not accepted in v1 — resolving them requires the timezone subsystem (see reserved codes below).
subject.time.out_of_range
Section titled “subject.time.out_of_range”422 — Subject time is outside the supported range (1800–2400 CE).
When you get it: the datetime parsed, but falls outside the range the ephemeris is validated for.
How to fix: supply a datetime between 1800 and 2400 CE. If you need dates outside this range, tell us — widening it is a data question, not an architectural one.
curl -s https://astral-external-api-iksoi6t3nq-ue.a.run.app/v1/western/positions \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"subject": {"datetime": "1499-01-01T00:00:00Z"}}'subject.location.invalid_coordinates
Section titled “subject.location.invalid_coordinates”422 — Coordinates out of range.
When you get it: latitude is outside −90…90 or longitude is outside −180…180.
How to fix: send decimal degrees within range, positive north/east. A common cause is swapped latitude/longitude.
settings.ayanamsa.required
Section titled “settings.ayanamsa.required”422 — ayanamsa is required when zodiac is sidereal.
When you get it: settings.zodiac is "sidereal" but no settings.ayanamsa was given. There is no universal default ayanamsa — the choice shifts every longitude in the chart and can move bodies across sign boundaries — so the API refuses to pick a school of practice on your behalf.
How to fix: pass one of lahiri, raman, or krishnamurti.
curl -s https://astral-external-api-iksoi6t3nq-ue.a.run.app/v1/western/positions \ -H "Authorization: Bearer $API_KEY" \ -H "Content-Type: application/json" \ -d '{"subject": {"datetime": "2000-01-01T00:00:00Z"}, "settings": {"zodiac": "sidereal"}}'settings.ayanamsa.not_applicable
Section titled “settings.ayanamsa.not_applicable”422 — ayanamsa applies only to the sidereal zodiac.
When you get it: settings.ayanamsa was given while zodiac is "tropical" (explicitly, or by default). An ayanamsa would have no effect on a tropical calculation, and a setting that silently does nothing is a bug waiting to be discovered — so it is rejected instead.
How to fix: remove ayanamsa, or set "zodiac": "sidereal" if a sidereal chart was the intent.
Server errors
Section titled “Server errors”server.internal
Section titled “server.internal”500 — Internal error.
When you get it: a bug on our side. These page us automatically.
What to do: retry once; if it persists, contact us with the request_id.
server.overloaded
Section titled “server.overloaded”503 — Server is shedding load.
When you get it: the calculation pool is saturated and the API is refusing work rather than queueing it into timeout territory.
What to do: retry with exponential backoff and jitter. The request itself is fine — resubmit it unchanged. (Retry-After is planned; see the note above.)
server.calc_timeout
Section titled “server.calc_timeout”503 — Calculation timed out.
When you get it: the calculation exceeded its per-request time budget, usually under heavy load.
What to do: same as server.overloaded — the request is valid, retry with backoff.
Reserved codes (timezone and geocoding release)
Section titled “Reserved codes (timezone and geocoding release)”The following codes are reserved and not yet live. They ship with the timezone/geocoding subsystem, which will let subjects be expressed as a local time plus a place instead of an offset datetime plus coordinates. They are documented now so the contract is stable before you need it.
subject.time.ambiguous_local_time(422) — the local time occurred twice, e.g. during a DST fall-back hour.subject.time.nonexistent_local_time(422) — the local time never occurred, e.g. inside a DST spring-forward gap.subject.location.ambiguous_place(422) — a place name matched more than one location.
Ambiguity errors will carry a candidates extension member listing each valid resolution, so a client can disambiguate programmatically or present the choice to a user:
{ "type": "https://docs.astralastrologyapi.com/errors/subject.time.ambiguous_local_time", "title": "Local time is ambiguous", "status": 422, "code": "subject.time.ambiguous_local_time", "detail": "1992-10-25T01:30:00 occurred twice in America/New_York (DST fall-back). Specify an explicit offset.", "candidates": [ { "utc_offset": "-04:00", "utc_datetime": "1992-10-25T05:30:00Z" }, { "utc_offset": "-05:00", "utc_datetime": "1992-10-25T06:30:00Z" } ], "request_id": "req_8fK2x1"}These are deliberately errors, not silent guesses: for an ambiguous birth time, refusing to pick is the correct answer. Until the release, the inputs that could trigger them (local_datetime, location.place) are rejected as unknown fields with request.malformed.
API spec v0.1.0 · docs 0e4f5ec