Skip to content

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.

curl https://api.astrolinkers.com/v1/plans
{
  "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.

curl -H "Authorization: Bearer $ASTROLINKERS_API_KEY" \
  https://api.astrolinkers.com/v1/tenant/plan
{
  "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.