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.
What each endpoint costs
Section titled “What each endpoint costs”| 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:
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_chargedThe composed natal call charges 2:
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_chargedA window scan — transits/events — charges 3, whatever the window’s size or the number of hits found:
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.)
Errors cost nothing
Section titled “Errors cost nothing”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.
Checking your usage
Section titled “Checking your usage”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.
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—requestsandcreditsfor 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 carriesrequests,credits, and how many of those requests endederrors_4xxorerrors_5xx(both already included inrequests).source— which aggregate store answered the read (memoryorfirestore); 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.
Rate limiting
Section titled “Rate limiting”Planned pricing
Section titled “Planned pricing”API spec v0.1.0 · docs 1123b41