Skip to content
AstroWay/api v2.201.6 · docs
all systems operational

Credits & Rate Limits

AstroWay uses a credit-based model instead of a simple request counter.

This is fairer: lightweight endpoints (/planets) cost 10 credits, heavy ones (/rectification) - 500.

Credits per month depend on your plan:

PlanCredits/moRate limit
Free10,00010 req/min
Indie50,00030 req/min
Starter200,000120 req/min
Pro800,000400 req/min
Business3,500,0001,000 req/min
EnterpriseCustomCustom

When credits run out - subsequent requests return 402 Payment Required (Free) or trigger overage billing (Indie+).

Rate limit uses a sliding window. If you fire 120 requests in 10 seconds, the next 50 seconds on that key return 429.

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.

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 the other public namespaces: /v1/public/*, /v1/embed/*, /v1/i18n/*.

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/
ModeLimit
Without the header30 requests / hour per IP
With the header300 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: site
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 297
X-RateLimit-Counters: ip=597/600, site=297/300

The 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/hour does not stretch that far.

Full transparent per-endpoint table with all 760+ endpoints and their costs - at Per-endpoint Cost. DivineAPI / AstrologyAPI / Prokerala don’t publish anything similar. Quick reference:

TierCreditsTypical timeExamples
110< 50 ms/planets, /moon-voc, /iching
22050–200 ms/chart, /harmonics, /horary
350200–500 ms/synastry, /progressions, /acg
4100> 500 ms/transit-calendar, /forecast-calendar
5250seconds/rectification/trutine
6500up to 120s/rectification

Every response includes:

X-Credits-Used: 20
X-Credits-Remaining: 9980
X-Credits-Limit: 10000
X-Credits-Reset: 2026-05-01T00:00:00Z
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 118
X-RateLimit-Reset: 1714521600
X-RateLimit-Origin-Limit: 60
X-RateLimit-Origin-Remaining: 59
X-Request-Id: 8aec43e9bc749b37e998a489bbad1757
  • X-Credits-Used: credits charged for this request
  • X-Credits-Remaining: remainder of monthly budget
  • X-Credits-Limit: full monthly credit budget (per plan)
  • X-Credits-Reset: ISO-8601 balance reset date
  • X-RateLimit-*: current sliding window state (standard format)
    • X-RateLimit-Scope: which counter is currently the tightest: ip, site or key. A value of key means 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 with X-AstroWay-Site-URL, every counter the request was measured against, as scope=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, then Reset moves to the next timestamp. A client that waits for Reset and 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-Limit and X-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 a 429, and the message names the entry that ran out.
  • X-Subscription-Status and X-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 in X-Subscription-Grace-Ends, after which they get 402 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

You can check your current credit balance three ways - from fastest to most detailed:

  1. On every response: the X-Credits-Remaining / X-Credits-Used / X-Credits-Limit headers come back on any request (even errors). Nothing extra to call - your balance is always in front of you.
  2. On demand: GET /v1/keys/usage with your X-Api-Key returns credits (used / remaining / limit / reset), usage (requests for today / week / month), and your top-20 endpoints over 30 days. One call, no separate admin key.
  3. In the dashboard: api.astroway.info/dashboard/billing shows your balance, a usage chart, and a per-endpoint breakdown in real time.
Terminal window
curl https://api.astroway.info/v1/keys/usage \
-H "X-Api-Key: $AW_KEY"

Identical requests within 5 minutes return from cache without consuming credits. This is a separate header:

X-Cache: HIT
X-Credits-Used: 0

What 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.

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.

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.

{
"error": {
"code": "credits_exhausted",
"message": "Monthly credit budget exhausted. Upgrade plan or wait for reset.",
"credits_remaining": 0,
"credits_reset": "2026-05-01T00:00:00Z",
"upgrade_url": "https://api.astroway.info/dashboard/billing"
}
}

Options:

  • Upgrade plan: immediate new budget
  • Overage (Starter/Pro): auto-enabled unless disabled in settings
  • Wait for reset (Free): 1st of next month
{
"error": {
"code": "rate_limit_exceeded",
"message": "Too many requests. Retry after 12 seconds.",
"retry_after_seconds": 12
}
}

Retry-After: 12 header - standard HTTP, your HTTP client should understand it.

Correct handling:

async function callWithRetry(endpoint: string, body: object) {
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'X-Api-Key': key, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
if (response.status === 429) {
const retryAfter = Number(response.headers.get('Retry-After') ?? 1);
await new Promise((r) => setTimeout(r, retryAfter * 1000));
return callWithRetry(endpoint, body);
}
return response.json();
}

Before integrating, estimate monthly credits:

monthly_credits = daily_active_users × calls_per_user × avg_cost_per_call × 30

Example: 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
→ Enterprise (custom)

Budget calculator - on the Pricing page.

Was this helpful?
Suggest an edit

Last updated: