Authentication¶
Every endpoint outside /healthz, /readyz, /version, /metrics,
/docs, /redoc and /openapi.json requires a valid 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:
- JWKS endpoint —
JWT_JWKS_URLconfigured. Keys are fetched and cached (JWT_JWKS_CACHE_TTL_SECONDS, default 600 s); the token'skidheader selects which key to use. This is the production path — it supports rotation without a redeploy. - Static PEM —
JWT_PUBLIC_KEYenv var. Useful for single-issuer environments where the IdP is fixed. - 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:
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> |