Plans and usage¶
Free · all plans
Scopes:
keys:manage—GET /v1/api-keys/{key_id}/usage,GET /v1/api-keys/{key_id}/usage/endpoints,GET /v1/tenant/usage,GET /v1/tenant/usage/endpoints- no scope —
GET /v1/plans,GET /v1/tenant/plan,POST /v1/tenant/plan
What a tier costs and allows, which tier your tenant is on, and how much of it you have spent. Five endpoints, all tenant-scoped: they answer about your tenant and never about anyone else's, because the tenant is taken from your credentials rather than from the URL.
The Free plan is commercial
You may use the Free plan in commercial products — including apps with paying users —
within its limits and without an uptime commitment. It has no LLM features: LLM-backed
endpoints answer 403 plan_feature_unavailable
(Paid (LLM) endpoints). Hobby and Pro are coming soon.
GET /v1/plans — the catalog¶
The static list of tiers, ordered free → hobby → pro. No authentication and no tenant: it is the same answer for everybody, so it can back a pricing page.
{
"plans": [
{
"tier": "free",
"display_name": "Free",
"monthly_price_usd": 0.0,
"rate_limit_capacity": 60.0,
"rate_limit_refill_per_second": 2.0,
"llm_cost_cap_per_hour_usd": 0.0,
"status": "available",
"features": [
"Natal charts + talent profile",
"Template-based interpretations (no LLM rewrite)",
"Pair compatibility (synastry + Ashtakoota)",
"PDF reports",
"60 req/burst, 2 req/sec sustained",
"1000 requests / day, 10 000 / month",
"1 active API key"
]
}
]
}
| Field | Type | Notes |
|---|---|---|
tier |
enum | free, hobby, pro. The stable key — branch on this, not on display_name. |
display_name |
string | For a pricing page. |
monthly_price_usd |
float | 0.0 on Free. |
rate_limit_capacity |
float | Token-bucket size, i.e. the burst you may spend at once. See Rate limits. |
rate_limit_refill_per_second |
float | Sustained rate once the burst is gone. |
llm_cost_cap_per_hour_usd |
float or null |
Hourly ceiling on LLM spend. 0.0 on Free, which is why Free serves template interpretations and not LLM-rewritten ones. |
status |
enum | available or coming_soon. A coming_soon tier is listed but cannot be switched to — show it, do not offer it. |
features |
string[] | Marketing copy, in display order. Not a contract: do not parse it to decide what your integration may call. |
At the time of writing Hobby ($9) and Pro ($49) are coming_soon; only Free is available.
Read status rather than trusting that sentence.
GET /v1/tenant/plan — which tier you are on¶
Required: any valid credential. The tenant comes from the token.
{
"tenant_id": "tenant_01HZY...",
"display_name": "Acme Astrology",
"plan": { "tier": "free", "display_name": "Free", "…": "…" },
"plan_updated_at": "2026-05-17T09:12:44Z",
"created_at": "2026-04-02T18:30:01Z"
}
The tenant row is created on first read. A caller who has never had one gets a Free-tier tenant back rather than a 404, so there is no separate "sign up for the API" step.
POST /v1/tenant/plan — switch tier¶
curl -X POST -H "Authorization: Bearer $ASTROLINKERS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"plan_tier": "free"}' \
https://api.astrolinkers.com/v1/tenant/plan
Returns the same TenantPlanResponse as the GET.
| Outcome | Status | detail.reason |
|---|---|---|
| Switched | 200 | — |
plan_tier is not one of the three |
422 | invalid_plan_tier |
Tier is coming_soon, or is paid and you are not an admin |
403 | plan_not_available |
| Tenant row disappeared between the create and the update | 404 | tenant_not_found |
Downgrading to Free is self-service; moving to Hobby or Pro requires the admin scope.
That is deliberate while billing is not wired up: nothing should be able to put a tenant on a
paid tier except us.
GET /v1/tenant/usage — hourly totals¶
Required scope: keys:manage. Sums every active API key on the tenant.
| Parameter | Type | Default | Notes |
|---|---|---|---|
since |
datetime | until − 7 days |
ISO 8601 with an offset. A naive datetime is a 422. |
until |
datetime | now | Must be after since. |
The window may not exceed 90 days. Both usage endpoints reject a bad range with a 422:
detail.reason |
Meaning |
|---|---|
naive_datetime |
since or until arrived without a timezone offset. |
invalid_range |
until is not after since. |
range_too_large |
More than 90 days; detail.max_days says the limit. |
curl -H "Authorization: Bearer $ASTROLINKERS_API_KEY" \
"https://api.astrolinkers.com/v1/tenant/usage?since=2026-05-17T00:00:00Z&until=2026-05-18T00:00:00Z"
{
"since": "2026-05-17T00:00:00Z",
"until": "2026-05-18T00:00:00Z",
"total_requests": 184,
"total_errors": 3,
"buckets": [
{
"bucket_hour": "2026-05-17T12:00:00Z",
"requests": 184,
"errors_4xx": 2,
"errors_5xx": 1,
"latency_p95_ms": 42.5
}
]
}
| Field | Type | Notes |
|---|---|---|
bucket_hour |
datetime | Start of the hour, UTC. Hours with no traffic are absent, not zero-filled — fill the gaps yourself if you are drawing a chart. |
requests |
integer | Including the ones that failed. |
errors_4xx / errors_5xx |
integer | Kept apart because they mean different things: 4xx is usually your integration, 5xx is ours. |
latency_p95_ms |
float or null |
null when the bucket carries too few samples to have a meaningful p95. |
GET /v1/tenant/usage/endpoints — where the traffic went¶
Required scope: keys:manage. Same window parameters, plus:
| Parameter | Type | Default | Notes |
|---|---|---|---|
limit |
integer 1…100 |
20 |
How many route templates to return, busiest first. |
{
"since": "2026-05-17T00:00:00Z",
"until": "2026-05-18T00:00:00Z",
"total_requests": 184,
"total_errors": 3,
"buckets": [
{
"endpoint_path": "/v1/western/charts/{chart_id}/summary",
"requests": 128,
"errors_4xx": 1,
"errors_5xx": 0,
"latency_p95_ms": 38.25
}
]
}
endpoint_path is the route template, not a concrete URL: every chart id collapses into
{chart_id}, so the breakdown has as many rows as you have endpoints and not as many as you
have charts.
total_requests counts the whole window, so it can exceed the sum of the returned buckets
when limit cuts the tail off. That difference is your long tail, not a bug.
Per-key rather than per-tenant¶
The same two shapes exist scoped to a single API key —
GET /v1/api-keys/{key_id}/usage and GET /v1/api-keys/{key_id}/usage/endpoints. See
API keys. The tenant endpoints on this page are the sum over every active key.