Skip to content

Credits & metering

Usage is metered in credits. Every successful response reports its own cost inline, in meta.credits_charged:

{
"data": { "positions": [ /* ... */ ] },
"meta": {
"calc_version": "0.1.1",
"settings_resolved": { /* ... */ },
"subject_resolved": { /* ... */ },
"credits_charged": 1
}
}

meta.credits_charged is present on every success response. Because the charge rides back in the response itself, your own request logs are your usage audit trail — sum credits_charged and you have your usage total.

Endpoint Credits
POST /v1/western/positions 1
POST /v1/western/houses 1
POST /v1/western/aspects 1
POST /v1/western/moon 1 — or 2 with a window
POST /v1/western/natal 2
POST /v1/western/synastry 2
POST /v1/western/composite 2
POST /v1/western/transits 2
POST /v1/western/progressions 2
POST /v1/western/returns 2
POST /v1/western/transits/events 3
GET /v1/usage 0
GET /health, GET /ready Free, unmetered

natal composes positions, houses, aspects, and distributions into one response; at 2 credits it costs less than positions + houses + aspects billed separately. moon charges 1 for a state read and 2 when you also pass a window for events. GET /v1/usage charges 0 but still counts as a request; /health and /ready are unmetered and never counted.

Each charge is reported in the response, so you can confirm any row for yourself. positions charges 1:

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 .meta.credits_charged

The composed natal call charges 2:

Terminal window
curl -s https://astral-external-api-iksoi6t3nq-ue.a.run.app/v1/western/natal \
-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.credits_charged

A window scan — transits/events — charges 3, whatever the window’s size or the number of hits found:

Terminal window
curl -s https://astral-external-api-iksoi6t3nq-ue.a.run.app/v1/western/transits/events \
-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 }
},
"window": { "from": "2026-01-01T00:00:00Z", "to": "2026-04-01T00:00:00Z" }
}' | jq .meta.credits_charged

(The examples assume API_KEY is exported — see Authentication.)

A 4xx or 5xx response charges 0 credits — a validation rejection, a 401, a malformed body, a 500 on our side, a shed-load 503. You pay for answers, not attempts, so retrying a 503 costs nothing until the retry succeeds. Error responses are problem documents with no meta envelope, so they carry no credits_charged field to misread as a charge.

Errors still count as requests, though. Every completed call — success or failure — increments the request counter reported by GET /v1/usage, so requests and credits move independently.

GET /v1/usage returns the calling key’s own totals. It reads the usage aggregate for whichever key is in your Authorization header — a key can only ever see its own usage — and is itself metered at 0 credits.

Terminal window
curl -s https://astral-external-api-iksoi6t3nq-ue.a.run.app/v1/usage \
-H "Authorization: Bearer $API_KEY" | jq .
{
"data": {
"source": "firestore",
"month_to_date": { "requests": 42, "credits": 58 },
"daily": [
{
"date": "2026-07-09",
"requests": 42,
"credits": 58,
"errors_4xx": 2,
"errors_5xx": 0
}
]
},
"meta": {
"calc_version": "0.1.1",
"credits_charged": 0,
"request_id": "req_8fK2a1b2c3d4e5f6"
}
}
  • month_to_date — requests and credits for the current UTC calendar month, from the 1st through today.
  • daily — one row per day that had activity, within a 30-day lookback window, ascending by UTC date. Each row carries requests, credits, and how many of those requests ended errors_4xx or errors_5xx (both already included in requests).
  • source — which aggregate store answered the read (memory or firestore); an envelope detail, not something to branch on.

Usage events are buffered and folded into the totals on a flush (roughly every 30 seconds), so a call made moments ago may not be reflected yet, and a request made just before UTC midnight can land on the next day’s row.

API spec v0.1.0 · docs 1123b41