Credits & Rate Limits
AstroWay uses a credit-based model instead of a simple request counter.
This is fairer: a light endpoint costs a few credits, a heavy calculation hundreds. What each one costs is on Per-endpoint Cost.
A key runs into two independent limits: credits per cycle and requests per minute. You can have credits left and still get a 429 for speed, and the other way round.
Plan limits
Section titled “Plan limits”The table is built from the same tier configuration the server counts limits by, so it cannot drift from what you get.
| Plan | Credits a cycle | Requests a minute | Keys | Overage per 10,000 credits |
|---|---|---|---|---|
| Free | 10,000 | 10 | 1 | none |
| Indie | 50,000 | 30 | 3 | $5 |
| Starter | 200,000 | 120 | 5 | $5 |
| Pro | 800,000 | 400 | 20 | $3 |
| Business | 3,500,000 | 1,000 | no limit | $2 |
| HD Pack (pack) | 50,000 | 60 | 5 | $5 |
| Esoteric Pack (pack) | 200,000 | 60 | 5 | $5 |
| Vedic Pack (pack) | 100,000 | 100 | 5 | $5 |
| Reports Pack (pack) | 1,500,000 | 200 | 5 | $3 |
| Enterprise | by contract | |||
- A cycle is a month from your account’s date, not a calendar month. The
X-Credits-Resetheader on every response says when the next one starts. There is no separate daily limit: the whole cycle budget can be spent in a day. - An annual subscription carries unspent credits into the next month, up to the plan’s monthly amount. What was carried shows in
X-Credits-Rollover. - Keys is how many active keys the account can hold. Credits and the speed limit are shared by every key on the account, so a second key is not a second budget.
Requests a minute
Section titled “Requests a minute”The speed limit is a sliding 60-second window. The server remembers the time of every accepted request in the last minute, and a new one goes through while there are fewer than your plan’s limit.
- One window per account. Every key on an account shares one window, so rotating a key or creating a new one does not reset it.
- A refused request does not count. A
429takes no slot in the window and does not move it. - An accepted request counts even if it ends in an error. A request answered
400 INVALID_INPUTtakes a slot in the window, though it spends no credits: credits are charged only for a2xxresponse. - Identical requests served from cache pass through the window too, but cost no credits, see Caching.
When a key carries a list of allowed origins, each entry in that list gets a share of this limit: the key’s limit divided by the number of entries, rounded up. One site with an embedded widget therefore cannot eat the quota the key’s owner pays for, and the owner’s other domains keep working. The key’s overall limit does not grow: the shares divide it rather than add to it.
The counter is kept per entry, not per host that arrived. An entry of *.example.com is one counter for every subdomain beneath it, or the limit could be walked around by inventing a new subdomain per request. With a single entry in the list the share equals the limit and nothing changes.
A publishable pk_ key has one more limit on top of the minute window: 120 requests an hour from one visitor address. The key sits in page source, and without this one script on one address could spend the budget for every visitor.
When credits run out: overage
Section titled “When credits run out: overage”On Free, a request past the cycle budget gets 429 CREDITS_EXHAUSTED until the next cycle starts.
On a paid plan the request is still served, and the excess counts as overage:
- Overage is counted in buckets of 10,000 credits at your plan’s rate from the table above. The request that opens a new bucket adds the full rate to the bill, and the other requests in that bucket cost $0.
- The
X-Overage-Charge-Usdheader says what this request added, andX-Overage-Usdthe total for the cycle so far. - A request that ended in an error (
4xx,5xx) is not counted as overage. - The invoice for last month’s overage arrives at the account’s email address early the next month.
- The spend cap limits overage for the whole account, every key together. The plan sets a default, and you change it in the dashboard under Billing. When the next request would go past the cap it gets
429 SPEND_CAP_REACHED. A cap of $0 turns overage off entirely: after the plan budget, requests simply stop.
A key budget works differently: a key you gave its own credit budget gets 429 KEY_BUDGET_EXHAUSTED once it is spent and never moves to overage. A key published in page source or handed to an automation therefore cannot cost more than you allowed.
Public endpoints (no key)
Section titled “Public endpoints (no key)”The 14 reference dictionaries /v1/reference/* (signs, planets, houses, aspects, elements, modalities, polarities, dignities, decans, nakshatras, lots, asteroids, zodiac-systems, glyphs) are served publicly without an X-Api-Key and cost 0 credits. Limit - 30 requests / hour per IP. The same data is also exposed as MCP Resources at astroway://reference/<slug> for LLM agents.
The same limit applies to /v1/public/* and /v1/i18n/*. The /v1/embed/* widgets have a bucket of their own, 120 requests an hour per IP.
Quota for server-side rendering
Section titled “Quota for server-side rendering”30 requests per hour is sized for a browser: every visitor brings their own IP and their own quota. When the calls come from your server instead (a WordPress plugin, SSR, a cache warmer), one address fetches on behalf of every visitor and burns the limit in a handful of page loads.
For that case, send the X-AstroWay-Site-URL header with your site address:
X-AstroWay-Site-URL: https://example.com/| Mode | Limit |
|---|---|
| Without the header | 30 requests / hour per IP; /v1/embed/* widgets have a separate 120 / hour per IP |
| With the header | 300 requests / hour per site, 600 / hour ceiling per IP |
The header is allowed in CORS and is a declaration, not a credential, which is why the IP ceiling stays: however many domains one address names, no more than 600 requests per hour will come out of it. Both counters are needed at once. The site counter binds when one site calls from many addresses (CDN, load balancer); the IP counter binds when many names come from one address.
One call is not always one unit. A heavy public endpoint spends more of the same bucket rather than getting a bucket of its own. POST /v1/public/synastry costs 3, because it computes two charts and a 45-cell aspect matrix, so the practical ceiling for it is 10 an hour from an undeclared address, 100 per declared site and 200 per address. Everything else costs 1. When a call costs more than one unit the response says so in X-RateLimit-Cost, and a call that costs more than the bucket has left is refused rather than allowed to overdraw.
The X-RateLimit-Scope response header (ip or site) tells you which counter is currently the tightest, and the remaining X-RateLimit-* headers describe that one, which is one counter out of two. The other is in X-RateLimit-Counters:
X-RateLimit-Scope: siteX-RateLimit-Limit: 300X-RateLimit-Remaining: 297X-RateLimit-Counters: ip=597/600, site=297/300The format is scope=remaining/limit, comma separated. It is sent only when there really are two counters: an anonymous call carrying X-AstroWay-Site-URL. A call with a paid key is counted against the key at your plan’s rate, where there is one counter and no header. Read it if you render several domains server-side from one address: each domain gets its own site bucket, the IP ceiling is shared between them, and it is the one that runs out first.
Cache responses on your side: a daily horoscope does not change during the day, and caching it until midnight removes the limit question entirely.
A paid key on the public endpoints. /v1/public/* and /v1/embed/* work without a key, but if you send one (X-Api-Key) the request is counted at your plan’s rate instead of the anonymous quota, and the response carries no free-tier watermark. This is what server-side rendering needs: one machine fetches on behalf of every visitor, and the anonymous 300 an hour does not stretch that far.
Pricing table
Section titled “Pricing table”Full transparent per-endpoint table with all 865+ endpoints and their costs - at Per-endpoint Cost. DivineAPI / AstrologyAPI / Prokerala don’t publish anything similar. Quick reference:
| Tier | Credits | Typical time | Examples |
|---|---|---|---|
| 1 | 10 | < 50 ms | /planets, /moon-voc, /iching |
| 2 | 20 | 50–200 ms | /chart, /harmonics, /horary |
| 3 | 50 | 200–500 ms | /synastry, /progressions, /acg |
| 4 | 100 | > 500 ms | /transit-calendar, /forecast-calendar |
| 5 | 250 | seconds | /rectification/trutine |
| 6 | 500 | up to 120s | /rectification |
Response headers
Section titled “Response headers”Every response includes:
X-Credits-Used: 20X-Credits-Remaining: 9980X-Credits-Limit: 10000X-Credits-Reset: 2026-05-01T00:00:00ZX-RateLimit-Limit: 120X-RateLimit-Remaining: 118X-RateLimit-Reset: 1714521600X-RateLimit-Origin-Limit: 60X-RateLimit-Origin-Remaining: 59X-Request-Id: 8aec43e9bc749b37e998a489bbad1757X-Credits-Used: credits charged for this requestX-Credits-Remaining: remainder of monthly budgetX-Credits-Limit: full monthly credit budget (per plan)X-Credits-Reset: ISO-8601 balance reset dateX-RateLimit-*: current sliding window state (standard format)X-RateLimit-Scope: which counter is currently the tightest:ip,siteorkey. A value ofkeymeans the request was counted against your key at your plan’s rate, not against the anonymous quota.X-RateLimit-Counters: on a public endpoint called withX-AstroWay-Site-URL, every counter the request was measured against, asscope=remaining/limit. The headers above describe the tightest one only.X-RateLimit-Reset: when the oldest request leaves the window, i.e. when one slot frees up. It is not the moment the whole bucket reopens: one request goes through after it, thenResetmoves to the next timestamp. A client that waits forResetand then sends a burst gets a 429 again, so send one request at a time or cache on your side.- A rejected request (429) does not spend quota and does not move the window: only successful requests fill the counter.
X-RateLimit-Origin-LimitandX-RateLimit-Origin-Remaining: present only when the request was admitted by an entry in the key’s allowed-origins list, and they report the share given to that entry. Exhausting a share is also a429, and the message names the entry that ran out.
X-Subscription-StatusandX-Subscription-Grace-Ends: present only after your paid period has ended while its 3-day grace period is still running (X-Subscription-Status: grace). Requests are served on your plan until the time inX-Subscription-Grace-Ends, after which they get402 SUBSCRIPTION_EXPIRED. If your client logs headers, this is the earliest signal that the plan needs renewing.X-Request-Id: this request’s identifier, 32 hexadecimal characters; quote it in support requests
Checking your usage: three ways
Section titled “Checking your usage: three ways”You can check your current credit balance three ways - from fastest to most detailed:
- On every response: the
X-Credits-Remaining/X-Credits-Used/X-Credits-Limitheaders come back on any request (even errors). Nothing extra to call - your balance is always in front of you. - On demand:
GET /v1/keys/usagewith yourX-Api-Keyreturnscredits(used / remaining / limit / reset),usage(requests for today / week / month), and your top-20 endpoints over 30 days. One call, no separate admin key. - In the dashboard: api.astroway.info/dashboard/billing shows your balance, a usage chart, and a per-endpoint breakdown in real time.
curl https://api.astroway.info/v1/keys/usage \ -H "X-Api-Key: $AW_KEY"Caching
Section titled “Caching”Identical requests within 5 minutes return from cache without consuming credits. This is a separate header:
X-Cache: HITX-Credits-Used: 0What counts as “identical”:
- Same endpoint
- Same JSON body (byte-for-byte after whitespace normalization)
- Same API key
This is safe because all endpoints are deterministic - same input always produces same output.
Streaming and webhooks
Section titled “Streaming and webhooks”Streaming (/stream/*)
Section titled “Streaming (/stream/*)”The 10 streaming endpoints are priced as Tier 1: 10 credits per call. Each call recomputes planetary positions, so it is a live compute, not a cached lookup - there is no discounted “per-tick” rate.
Every response carries two fields for efficient polling:
tickSeconds: recommended poll interval;nextEventAt: when the state next changes.
Polling faster than tickSeconds burns credits for nothing: the state won’t change until nextEventAt.
Webhooks (/webhooks/*)
Section titled “Webhooks (/webhooks/*)”Subscription-management CRUD calls (/webhooks/subscribe, list, get, delete, /test) cost Tier 1: 10 credits each. These are made rarely.
Webhook delivery (server → your URL) is not billed: credits are charged only for the CRUD management calls. 12 event types are supported (report-ready, transit-alert, eclipse-alert, dasha-change, sign-ingress, etc.).
Webhooks are available on the Pro plan and above.
What to do when limits are hit
Section titled “What to do when limits are hit”Every limit answers 429, and the error.code says which one. Only RATE_LIMIT is worth retrying after a pause; the others stay refused until the reset or until you change something.
CREDITS_EXHAUSTED: out of credits
Section titled “CREDITS_EXHAUSTED: out of credits”{ "ok": false, "error": { "code": "CREDITS_EXHAUSTED", "message": "Monthly credit limit reached (10000). Resets at 2026-11-01T00:00:00.000Z. Upgrade to Indie ($5/mo, 50K credits) for overage tolerance.", "details": { "upgrade_to": "indie", "upgrade_url": "https://api.astroway.info/pricing/" } }}The X-Credits-Reset header carries the same date. Options:
- Upgrade the plan: a new budget at once.
- Wait for the reset: your account’s monthly date, in
X-Credits-Reset.
On a paid plan this code does not appear: the request is served as overage instead, and X-Overage-Charge-Usd says what it added to the bill.
SPEND_CAP_REACHED: overage stopped by your cap
Section titled “SPEND_CAP_REACHED: overage stopped by your cap”The spend cap is a monthly ceiling on overage dollars for the whole account, all keys together. Set it in the dashboard under Billing. $0 means “never bill me past the plan”: when the plan credits run out, requests get this code instead of an overage charge.
{ "ok": false, "error": { "code": "SPEND_CAP_REACHED", "message": "Plan quota used, and the spend cap is $0, so requests stop here instead of billing overage. Raise the cap in the dashboard or wait until 2026-11-10T22:00:00.000Z.", "details": { "cap": 0, "used": 0, "projected": 3, "reset_at": "2026-11-10T22:00:00.000Z" } }}KEY_BUDGET_EXHAUSTED: one key spent its own budget
Section titled “KEY_BUDGET_EXHAUSTED: one key spent its own budget”A key can carry its own credit budget, set per key in the dashboard. It never turns into overage, so a key published in page source cannot cost more than you allowed. Other keys of the account keep working.
RATE_LIMIT: requests per minute exceeded
Section titled “RATE_LIMIT: requests per minute exceeded”{ "ok": false, "error": { "code": "RATE_LIMIT", "message": "Rate limit exceeded. Try again in 12s" }}The Retry-After: 12 header is standard HTTP, your HTTP client should understand it.
Correct handling: retry only RATE_LIMIT. Retrying every 429 loops forever on an exhausted budget, because those responses carry no Retry-After and do not clear by waiting a second.
async function callWithRetry(endpoint: string, body: object, attempts = 3) { const response = await fetch(endpoint, { method: 'POST', headers: { 'X-Api-Key': key, 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); const json = await response.json();
if (response.status === 429 && json.error?.code === 'RATE_LIMIT' && attempts > 1) { const retryAfter = Number(response.headers.get('Retry-After') ?? 1); await new Promise((r) => setTimeout(r, retryAfter * 1000)); return callWithRetry(endpoint, body, attempts - 1); }
return json;}Budget estimation in advance
Section titled “Budget estimation in advance”Before integrating, estimate monthly credits:
monthly_credits = daily_active_users × calls_per_user × avg_cost_per_call × 30Example: app with 500 DAU, each making 3 chart calculations + 1 synastry per day:
500 × (3 × 20 + 1 × 50) × 30 = 500 × 110 × 30 = 1,650,000 credits/mo→ BusinessBudget calculator - on the Pricing page.
- Errors: full error code reference
- Idempotency: how to retry safely