# Session Auth & Managed Subscriptions

Let users browse KinesteX views and chat with the AI trainer for free, while **workout generation** requires an active subscription that **your backend controls**. KinesteX stores each user's subscription status and enforces it server-side — the client app can't bypass it.

**Fully optional.** If you never set a subscription status, nothing changes for your users.

All management calls are **server-to-server** with your API key (`x-api-key` header). Never ship the API key in your app.

**The flow:**

1. **Mint a session** for the user, declaring their subscription status:

```bash
curl -X POST https://data.kinestex.com/api/sessions \
  -H "x-api-key: YOUR_API_KEY" -H "content-type: application/json" \
  -d '{"user_id": "user-123", "is_subscribed": false}'
# → { "session_id": "ksx_sess_…", "user_id": "user-123", "is_subscribed": false, "expires_at": "…" }
```

2. **Launch the KinesteX view** with that `session_id` as usual — the SDK authenticates server-side and the raw API key never reaches the client. The user can browse, complete onboarding, and chat freely.

3. **At the generation step**, KinesteX posts `{ "type": "open_subscription_flow", "source": "generate_workout" }` to your app instead of generating. Present your paywall on top of the KinesteX view.

4. **After the purchase**, grant the subscription from your backend, then tell the view the flow finished by posting `{ "subscription_result": "purchased" }` (or `"dismissed"`) into the webview. On `"purchased"` the parked generation resumes automatically — no extra tap.

**Managing subscriptions:**

| Action | Call |
|--------|------|
| Grant | `POST /api/user-subscriptions` `{"user_id": "user-123"}` — idempotent; creates the user if they've never launched |
| Revoke | `DELETE /api/user-subscriptions/user-123` — blocks generation from their next attempt |
| List active | `GET /api/user-subscriptions?limit=100&offset=0` |
| Set at session mint | `POST /api/sessions` with `"is_subscribed": true/false` — omit to leave the stored status unchanged |

Status changes take effect on the user's next request — no relaunch needed.

**Rules & enforcement:**
- Status is **tri-state**: never set → nothing is gated (default); `true` → generation allowed; `false` → generation blocked.
- Only generation is gated (creating, modifying, or getting a recommended workout). Q&A, results discussion, and starting an already-generated workout stay available.
- Enforcement is server-side: an unsubscribed generation attempt returns `403 {"code": "not_subscribed"}` and the view re-opens your subscription flow. Reporting `"purchased"` without actually granting on the backend will not unlock generation.
- For users with a backend-managed status, that status **overrides** the `isSubscribed` launch flag. Users without one keep the launch-flag behavior unchanged.

**Web/redirect integrations** (no host app): pass `subscriptionReturnUrl` in the launch config instead. KinesteX redirects there at the generation step; after purchase, send the user back with a fresh session — generation unlocks automatically.

For the client-side handshake (events, edge cases, per-platform code), see the [subscription gating guide](/docs/guides/guide-subscription-gating).

---
Source: https://www.kinestex.com/docs/trainer-api/trainer-api-subscriptions · Index: https://www.kinestex.com/llms.txt
