Timezones & ambiguity
The largest source of wrong birth charts in production systems is not the ephemeris — it is timezone and daylight-saving handling. An hour of error moves the Moon by roughly half a degree and the Ascendant by around fifteen degrees on average, which is routinely enough to change a rising sign or a house placement. This page states exactly what the API does and does not do with time, so that class of error stays visible and fixable.
Today’s contract: explicit offset, no guessing
Section titled “Today’s contract: explicit offset, no guessing”In v1, subject.datetime must be an ISO 8601 timestamp with an explicit UTC offset:
{ "subject": { "datetime": "1992-11-22T08:45:00+05:30" } }With that input the API does precisely two things:
- Converts to Universal Time for the ephemeris —
08:45:00+05:30becomes03:15:00Z. - Echoes both facts back in
meta.subject_resolved: the resolvedutc_datetimeand theutc_offsetyou supplied, so you can verify the conversion on every single response.
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": "1992-11-22T08:45:00+05:30" } }' | jq .meta.subject_resolvedThe API never infers an offset — not from coordinates, not from a timezone guess, not from your account settings. A datetime without one is rejected, with a stable error code rather than a silent assumption:
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": "1990-05-15T14:30:00" } }' | jq .codeWhy explicit-only is the strict core
Section titled “Why explicit-only is the strict core”Accepting a local wall-clock time and resolving the offset ourselves would be more convenient — and it is coming. But the design rule is strict core, forgiving edges: convenience is only acceptable when everything resolved on your behalf is echoed back where you can check it. Forgiveness without echo is silent guessing, and silent guessing is exactly how timezone bugs become “your API is wrong” tickets that neither side can diagnose. v1 ships the strict core first; the forgiving edges ship together with the echo and error contract that make them safe.
The fall-back problem, concretely
Section titled “The fall-back problem, concretely”Here is why offset resolution can never be fully automatic. On October 25, 1992, clocks in America/New_York fell back from daylight time to standard time at 2:00 a.m. As a result, the local time 1:30 a.m. occurred twice:
- once at offset
-04:00(EDT), which is1992-10-25T05:30:00Z, and - once, an hour later, at offset
-05:00(EST), which is1992-10-25T06:30:00Z.
A birth certificate that says “1:30 a.m.” does not say which one. The two candidate moments are a full hour apart — a different Moon degree and, most of the time, a different Ascendant. No API can resolve this from the wall-clock time alone; the ambiguity is a fact about the input, and the only honest behaviors are to require the offset (today’s contract) or to refuse loudly with both candidates spelled out (the upcoming one).
The mirror-image case also exists: during the spring-forward transition, wall-clock times inside the skipped hour never occurred at all.
What to do today
Section titled “What to do today”Until local-time input ships, offset resolution lives in your stack — where every mainstream platform already has the right tool, the IANA time zone database:
- Resolve the offset from an IANA zone at the birth place and time, not from a lookup of the place’s current offset. Historical transitions matter enormously for birth data: DST rules, zone boundary changes, and one-off political changes are all recorded in the tz database and nowhere else. Use
zoneinfo(Python),java.time(JVM),Luxonordate-fns-tz(JavaScript),chrono-tz(Rust), or your platform’s equivalent. - Decide ambiguous times explicitly. Most tz libraries let you choose the earlier or later instant for a fall-back time — make that choice deliberately, record it, and pass the resulting offset.
- Send the explicit offset in
subject.datetime. - Verify the echo. Assert that
meta.subject_resolved.utc_datetimeis the instant you intended — in development at minimum, ideally in an integration test. It is one comparison, and it catches the entire class of double-conversion bugs (applying the offset in your code and letting a datetime library apply it again is the classic one).
API spec v0.1.0 · docs 0e4f5ec