Subjects & settings
Every computation endpoint takes the same two objects. The subject says when and where the chart is for; the settings say how to calculate it. There is exactly one subject schema and one settings schema across the entire API — integrate them once and you have integrated every endpoint. There are no per-endpoint parameter spellings, no day/month/year/hour/min on one route and datetime on another.
Both objects travel in a JSON body on POST:
{ "subject": { "datetime": "1992-11-22T08:45:00+05:30", "location": { "latitude": 28.6139, "longitude": 77.2090 } }, "settings": { "zodiac": "tropical", "node_type": "true" }}subject is required; settings is optional and fully defaulted.
The subject object
Section titled “The subject object”datetime (required)
Section titled “datetime (required)”An ISO 8601 timestamp with an explicit UTC offset, for example 1992-11-22T08:45:00+05:30. The datetime is authoritative: the API converts it to Universal Time for the ephemeris exactly as given and never adjusts, guesses, or “helpfully” reinterprets it. A datetime without an offset is rejected with 422 and the error code subject.time.invalid_format — see Timezones & ambiguity for why the offset is required and how to derive it correctly.
location (optional)
Section titled “location (optional)”Coordinates in decimal degrees: latitude from −90 to 90 (south negative), longitude from −180 to 180 (west negative).
Datetime-only subjects are valid
Section titled “Datetime-only subjects are valid”Planetary positions depend on the moment, not on where the observer stands. If all you need is positions — to drive your own aspect logic, feed a model, or render a sign-only reading — omit location entirely:
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 echo reports "coordinates": null, so a stored response is unambiguous about whether a location was involved. Location-dependent outputs — house cusps and angles, when they ship — will require coordinates and will say so with a documented error code rather than inventing a default location.
What time_known means
Section titled “What time_known means”Every response echoes meta.subject_resolved.time_known. Today it is always true, because v1 requires a full timestamp. The field exists because unknown-birth-time subjects (date-only) are planned: when they arrive, time_known: false will mark charts where time-dependent outputs (houses, angles, a precise Moon degree) are omitted or degraded. Since the field is already in the echo, your response handling won’t need a schema change when that lands.
The settings object
Section titled “The settings object”All settings fields are optional, every field has a documented default, and the fully resolved set is echoed back on every response.
| Field | Type | Default | Notes |
|---|---|---|---|
zodiac |
string: tropical or sidereal |
tropical |
Sidereal requires ayanamsa. |
ayanamsa |
string: lahiri, raman, or krishnamurti |
none | Required if and only if zodiac is sidereal. Sending it with the tropical zodiac is a 422 (settings.ayanamsa.not_applicable); omitting it with sidereal is a 422 (settings.ayanamsa.required). |
node_type |
string: "true" or "mean" |
"true" |
A JSON string, not a boolean — "node_type": true is malformed. Also selects which node appears in the default bodies. |
bodies |
array of body names | the ten planets plus the lunar node | The default node is true_node, or mean_node when node_type is "mean". |
Supported bodies values (18): sun, moon, mercury, venus, mars, jupiter, saturn, uranus, neptune, pluto, true_node, mean_node, chiron, lilith_mean, ceres, pallas, juno, vesta.
Requesting the sidereal zodiac means naming the ayanamsa explicitly — there is no implied default, because the choice moves every longitude:
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" }, "settings": { "zodiac": "sidereal", "ayanamsa": "lahiri" } }' | jq .meta.settings_resolvedBoth request objects are strict: an unknown field is rejected with 400 (request.malformed) naming the offending key, never silently ignored. If a request appears to accept a setting, it actually took effect.
The response envelope
Section titled “The response envelope”Every successful response has the same two-key shape: data carries the endpoint-specific result, and meta carries provenance — identical fields on every endpoint.
{ "data": { "positions": [ { "body": "sun", "longitude": 240.0766, "sign": "sagittarius", "sign_longitude": { "degrees": 0, "minutes": 4, "seconds": 36 }, "element": "fire", "modality": "mutable", "speed": 1.0108, "retrograde": false, "declination": -20.167, "out_of_bounds": false } ] }, "meta": { "calc_version": "0.1.1", "settings_resolved": { "zodiac": "tropical", "node_type": "true", "bodies": ["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto", "true_node"] }, "subject_resolved": { "utc_datetime": "1992-11-22T03:15:00Z", "utc_offset": "+05:30", "coordinates": { "latitude": 28.6139, "longitude": 77.2090 }, "time_known": true }, "credits_charged": 1 }}(The positions array is truncated to one body here; a real response contains one entry per requested body.)
meta always carries:
calc_version— the calculation engine version that produced this result; identical inputs and identicalcalc_versionproduce byte-identical outputs.settings_resolved— the full settings actually used, defaults filled in.subject_resolved— what the API understood your subject to be:utc_datetime,utc_offset,coordinates(ornull),time_known.credits_charged— metering for this call, in-band rather than buried in a dashboard.
Errors use RFC 9457 application/problem+json with a stable, namespaced code field — the codes quoted on this page are contractual, not prose.
The echo contract
Section titled “The echo contract”meta.settings_resolved and meta.subject_resolved appear on every response, not only when you ask. This is deliberate. Silent resolution is how calculation APIs acquire “your API is wrong” tickets: a default you didn’t know about, or an offset conversion you couldn’t see, produces an output that disagrees with your expectation and gives you nothing to check it against. The echo converts that support ticket into a documentation lookup — compare settings_resolved with what you intended and subject_resolved.utc_datetime with the birth moment you meant, and the discrepancy identifies itself.
Send only a subject and watch the defaults come back resolved:
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-04:00", "location": { "latitude": 40.7128, "longitude": -74.0060 } } }' | jq .meta.settings_resolvedAPI spec v0.1.0 · docs 0e4f5ec