Skip to content

Your first chart data in 10 minutes

This page takes you from zero to planetary positions for a real birth moment, then shows you how to change calculation settings and what an error looks like. You need curl; jq is optional but makes the output easier to read.

The API is in early access: there is no self-serve signup yet, and keys are provisioned manually. If you have been onboarded, you were issued a test key (prefix ak_test_); if not, ask your Astral contact for one. Live keys use the ak_live_ prefix — the prefix is visible in logs so you can always tell which environment a request hit.

Export the key so the examples below can use it:

Terminal window
export API_KEY=ak_test_your_key_here

All requests authenticate with a single header: Authorization: Bearer $API_KEY. No OAuth, no token refresh.

The subject below is a birth in New York City on May 15, 1990 at 2:30 pm Eastern Daylight Time:

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

subject.location is optional for this endpoint — a datetime-only subject works, and positions are identical either way. If you send coordinates, they are validated and echoed back in meta.subject_resolved.

The response is the standard envelope: your results under data, provenance under meta. Abridged to a single position — the real response has one entry for each body in the resolved bodies list (by default, the ten planets plus the lunar node):

Response (abridged; values illustrative)
{
"data": {
"positions": [
{
"body": "sun",
"longitude": 54.6621,
"sign": "taurus",
"sign_longitude": { "degrees": 24, "minutes": 39, "seconds": 44 },
"element": "earth",
"modality": "fixed",
"speed": 0.9581,
"retrograde": false,
"declination": 18.9354,
"out_of_bounds": false
}
]
},
"meta": {
"calc_version": "0.1.1",
"settings_resolved": {
"zodiac": "tropical",
"node_type": "true",
"bodies": ["sun", "moon", ""]
},
"subject_resolved": {
"utc_datetime": "1990-05-15T18:30:00Z",
"utc_offset": "-04:00",
"coordinates": { "latitude": 40.7128, "longitude": -74.006 },
"time_known": true
},
"credits_charged": 1
}
}

Every celestial position, on every endpoint, uses this exact shape:

Field Meaning
body Stable English key: sun, moon, mercurypluto, plus true_node, mean_node, chiron, lilith_mean, ceres, pallas, juno, vesta.
longitude Ecliptic longitude in decimal degrees (0–360).
sign The zodiac sign the longitude falls in.
sign_longitude The same longitude as position within the sign, as degrees, minutes, seconds. Decimal and DMS are always both present — you never write the conversion.
element The sign’s element: fire, earth, air, or water.
modality The sign’s modality: cardinal, fixed, or mutable.
speed Rate of motion in degrees per day. Negative means the body is moving backward through the zodiac.
retrograde true when speed is negative.
declination Distance north (positive) or south (negative) of the celestial equator, in decimal degrees.
out_of_bounds true when the body’s declination exceeds the Sun’s maximum — the true obliquity of date (about 23.44 degrees).

meta is the provenance record. It answers “what exactly did you compute?” without a support ticket:

  • calc_version — the version of the numerical contract. Identical input plus identical calc_version means identical digits, across runs and machines. If the engine’s numbers ever change, this version changes. Details in determinism and calc_version.
  • settings_resolved — the complete settings actually used, with defaults filled in. Send no settings and you can see the defaults here: zodiac: "tropical", node_type: "true", and the default body list — the ten planets plus the lunar node (true_node by default; the response above trims the array for space). When your output differs from another provider’s, diff this object first — it is usually a defaults mismatch, not a calculation error.
  • subject_resolved — what your subject resolved to: utc_datetime (the actual moment computed — note 14:30 -04:00 became 18:30Z), the utc_offset applied, your echoed coordinates (null if you omitted location), and time_known.
  • credits_charged — what this call cost, in-band on every response. A positions call is 1 credit; you never have to reconcile a dashboard to know your burn rate.

Everything is defaulted, everything is overridable, and whatever you send comes back resolved in meta.settings_resolved. This request switches to the sidereal zodiac with the Lahiri ayanamsa and restricts output to three bodies — and drops location entirely, since a datetime-only subject is valid:

Terminal window
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" },
"settings": {
"zodiac": "sidereal",
"ayanamsa": "lahiri",
"bodies": ["sun", "moon", "mars"]
}
}'

meta.settings_resolved now echoes "zodiac": "sidereal" and "ayanamsa": "lahiri" — that echo is exactly what this page’s CI assertions check. The sun’s longitude shifts back by the ayanamsa (roughly 23.7 degrees in 1990), moving it from late tropical Taurus into early sidereal Taurus.

The rules are strict in both directions: ayanamsa is required when zodiac is sidereal, and rejected when it is tropical (error code settings.ayanamsa.not_applicable). node_type chooses between the true node (default) and the mean node. Unknown fields are a 400, never silently ignored.

Errors are RFC 9457 problem documents (application/problem+json), and every one carries a stable machine-readable code. The cheapest way to see one is to forget the key:

Terminal window
curl -s https://astral-external-api-iksoi6t3nq-ue.a.run.app/v1/western/positions \
-H "Content-Type: application/json" \
-d '{"subject": {"datetime": "1990-05-15T14:30:00-04:00"}}'
401 response
{
"type": "https://docs.astralastrologyapi.com/errors/auth.missing",
"title": "Missing bearer token",
"status": 401,
"code": "auth.missing",
"detail": "send Authorization: Bearer <api key>",
"request_id": "req_f5fdf870fdd14ce8ae5e5934969e6f4f"
}

Branch on code, not on detail — codes are a versioned catalog and will not change under you; detail strings may improve over time. request_id is unique per request; include it when you contact support and we can find the exact request in our logs. The full catalog is in the errors guide.

API spec v0.1.0 · docs 0e4f5ec