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>

  • app is one of epublisher, creative_studio, sync_vision, youtube_optimizer, or all_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
}
FieldTypeMeaning
tierstringEffective tier: free, pro, etc.
statusstringactive, inactive, or PayFast lifecycle state.
sourcestringall_access, direct, or none.
hasAccessbooleanTrue unless the caller has no matching subscription.
creditsRemainingnumber | nullWallet balance for the app. null for all_access.
grandfatheredbooleanLegacy SKU retired-into-grandfathered.
featuresobjectPer-app capability map derived from tier.
expiresAtstring | nullISO timestamp when the current period ends.

Errors

  • 400 — missing or invalid app.
  • 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.