Docs
Entitlement API
The single endpoint every Resonance spoke calls to decide what the signed-in user is allowed to do. Auth is the caller's own Supabase access token — the hub never issues spoke-specific keys.
Endpoint
GET https://reson8.life/api/public/entitlement?app=<app_key>
appis one ofepublisher,creative_studio,sync_vision,youtube_optimizer, orall_access.Authorization: Bearer <token>is required — the token is the user's Supabase access token, not a hub-issued API key.- CORS is
*; safe to call from any spoke domain. - Responses set
Cache-Control: private, max-age=60. Do not cache across users.
Request
GET /api/public/entitlement?app=creative_studio HTTP/1.1 Host: reson8.life Authorization: Bearer <supabase_access_token> Accept: application/json
Response (200)
{
"ok": true,
"app": "creative_studio",
"userId": "b2c1…",
"tier": "pro",
"status": "active",
"source": "direct",
"expiresAt": "2026-08-17T00:00:00.000Z",
"features": { "posterGeneration": true, "highRes": true },
"checkedAt": "2026-07-17T12:00:00.000Z",
"hasAccess": true,
"currentPeriodEnd": "2026-08-17T00:00:00.000Z",
"creditsRemaining": 42,
"grandfathered": false
}| Field | Type | Meaning |
|---|---|---|
tier | string | Effective tier: free, pro, etc. |
status | string | active, inactive, or PayFast lifecycle state. |
source | string | all_access, direct, or none. |
hasAccess | boolean | True unless the caller has no matching subscription. |
creditsRemaining | number | null | Wallet balance for the app. null for all_access. |
grandfathered | boolean | Legacy SKU retired-into-grandfathered. |
features | object | Per-app capability map derived from tier. |
expiresAt | string | null | ISO timestamp when the current period ends. |
Errors
400— missing or invalidapp.401— missing or invalid bearer token.500— server misconfigured or downstream lookup failed.
Never treat a 500 as "no access". Fall back to cached tier or a soft deny with retry, and surface the outage to the user.
Health probe
Unauthenticated. Spokes hit this on boot to confirm the hub is reachable and the schema version matches what they compiled against.
GET /api/public/entitlement/health
{
"ok": true,
"service": "reson8-entitlement",
"schemaVersion": "2026-07-17",
"supportedApps": ["epublisher","creative_studio","sync_vision","youtube_optimizer","all_access"],
"checkedAt": "2026-07-17T12:00:00.000Z"
}Versioning
Adding optional response fields is non-breaking. Removing or renaming a field bumps schemaVersion. Spokes should refuse to boot if the health probe reports a major version they do not support.