Skip to content

Authentication

Every endpoint outside /healthz, /readyz, /version, /metrics, /docs, /redoc and /openapi.json requires a valid bearer token.

Authorization: Bearer <token>

Two token formats

The API accepts two complementary credentials; both produce the same Principal and downstream scope guards do not branch on which was used.

Format Prefix Issued by Verified by Best for
JWT eyJ… (3-segment) Platform / IdP signing key Public key + claim check Internal services, short-lived sessions
API key ak_live_… The customer (dashboard / REST) Argon2id hash + DB lookup Customer-facing integrations

The middleware dispatches by prefix: tokens starting with ak_live_ go to the API-key verifier; anything else falls through to the JWT verifier. See API keys for the customer-facing CRUD surface.

Supported JWT signing schemes

  • RS256 (RSA 2048+)
  • ES256 (ECDSA P-256)
  • EdDSA (Ed25519)

HS256 (shared secret) is intentionally not supported — the API is asymmetric only.

Key resolution

The API resolves verification keys in this priority order:

  1. JWKS endpoint — JWT_JWKS_URL configured. Keys are fetched and cached (JWT_JWKS_CACHE_TTL_SECONDS, default 600 s); the token's kid header selects which key to use. This is the production path — it supports rotation without a redeploy.
  2. Static PEM — JWT_PUBLIC_KEY env var. Useful for single-issuer environments where the IdP is fixed.
  3. No verifier — neither configured. The middleware attaches a synthetic admin principal. Only for local development — refuses to enable in ENVIRONMENT=prod.

Required claims

Claim Required Notes
sub yes Stable identifier of the calling user / service.
iss yes Must equal JWT_ISSUER.
aud yes Must contain JWT_AUDIENCE (default astro-platform).
iat yes Issued-at, UTC seconds.
exp yes Expiry, UTC seconds. Tokens past exp are rejected.
scope recommended Space-separated scope strings. See below.
tenant_id recommended Used by tenant isolation + LLM cost cap.

A 30-second leeway is applied on nbf / exp to absorb modest clock skew.

Scopes

Routes declare the minimum scope they require; a missing scope answers 403. When you create an API key you choose its scopes; give each key only what it needs.

Scope Allows
charts:read Reading charts and everything computed from them: GET /v1/charts/{id} and its profiles (talent, career, varna), all /v1/vedic/* and /v1/western/charts/* GET routes, GET /v1/vedic/panchanga*, /v1/vedic/sky/ingresses, /v1/vedic/vaishnava/*, /v1/vedic/kp/lookup, GET /v1/geolocation/places, POST /v1/rectification/grid, POST /v1/vedic/relationships/*, POST /v1/vedic/ux/relationship-highlights, POST /v1/vedic/muhurta/*, and the paid /v1/llm/* routes
charts:write POST /v1/charts, DELETE /v1/charts/{id}, DELETE /v1/tenant/data
interpretations:read GET /v1/interpretations/{id}
interpretations:write POST /v1/interpretations and the paid POST /v1/interpretations/{natal,relationship,forecast}
compatibility:read GET /v1/compatibility/{id}, POST /v1/compatibility/pair-facts, POST /v1/western/relationships, POST /v1/western/ux/*
compatibility:write POST /v1/compatibility
reports:read GET /v1/reports/{id}
reports:write POST /v1/reports
feedback:read GET /v1/feedback/{id}, GET /v1/feedback/templates/{template_id}
feedback:write POST /v1/feedback
keys:manage POST/GET /v1/api-keys, POST /v1/api-keys/{id}/revoke, usage: GET /v1/api-keys/{id}/usage*, GET /v1/tenant/usage*
admin Every scope above plus cross-tenant operations. Never issued to customers.

Routes that need no scope (any valid credential): GET /v1/tenant/plan, POST /v1/tenant/plan (Free only — paid tiers need admin), /v1/auth/*. Public routes (no credential): GET /v1/plans, POST /v1/demo/charts, /healthz, /readyz, /version.

Forgot or change your password

  • Forgot it: use "Forgot password?" on the sign-in page. We e-mail a link that works once, for 30 minutes; a newer request replaces the older link. The form answers the same whether or not the address has an account.
  • Change it: in the dashboard, Account → Change password. You need the current password.
  • Either way, every other session is signed out; API keys keep working. Revoke a key separately if you think it leaked.

Data is reached with an API key

A dashboard session (the token you get from /v1/auth/login) only manages your account and keys. Charts and everything else are reached with an ak_live_… API key, and every such request counts towards your plan's quota.

Multiple scopes go in one space-separated string:

{
    "scope": "charts:read charts:write interpretations:write"
}

Tenant isolation

If your token carries tenant_id, every read is filtered to that tenant and every write is tagged with it. A token without tenant_id falls back to the default tenant — useful for single-tenant deployments, risky for multi-tenant SaaS. Always include tenant_id for production traffic.

Cross-tenant reads (chart A's tenant trying to read chart B owned by another tenant) return HTTP 404, not 403 — the API does not confirm or deny the existence of objects owned by other tenants.

Failure modes

Reason HTTP Body details.reason
Missing Authorization header 401 missing_bearer
Malformed JWT 401 invalid_token: <jwt_lib_message>
Expired token 401 invalid_token: signature has expired
Wrong issuer / audience 401 invalid_token: <issuer\|audience> mismatch
JWKS endpoint unreachable 503 auth_unavailable (after cache TTL)
Valid token, missing scope 403 forbidden_scope: <scope_required>