Subscription Plans Reference
Complete reference for Creatiq subscription plans, feature flags, rate limiting, usage tracking, and Stripe integration.
Plan Comparison
| Free | Pro | Premium | |
|---|---|---|---|
| Price | $0/month | $4/month | $8/month |
| Contents per month | 3 | 30 | Unlimited |
| AI generations per month | 5 | 100 | Unlimited |
| Storage | 100 MB | 5 GB | 50 GB |
Feature Flags
Each feature flag is gated by plan tier. The requireFeature middleware (server/middleware/feature-gate.ts) checks the user's current plan against the feature access map before allowing the request to proceed.
Feature Access by Plan
| Feature Flag | Free | Pro | Premium | Description |
|---|---|---|---|---|
pdf-to-h5p | -- | Yes | Yes | Generate H5P content from PDF documents |
video-to-h5p | -- | -- | Yes | Generate H5P content from video files |
image-hotspot | -- | Yes | Yes | Create image hotspot activities |
url-to-h5p | -- | Yes | Yes | Generate H5P content from a URL |
blooms-critique | -- | Yes | Yes | Bloom's Taxonomy critique for content |
differentiation | -- | Yes | Yes | Content differentiation by learner level |
bulk-generation | -- | Yes | Yes | Generate multiple content items at once |
lesson-bundle | -- | -- | Yes | Bundle multiple activities into a lesson |
branching-scenario | -- | -- | Yes | Create branching scenario content |
analytics-dashboard | -- | Yes | Yes | Access the analytics dashboard |
ai-recommendation | -- | -- | Yes | AI-powered content recommendations |
content-remixer | -- | Yes | Yes | Remix existing content into new formats |
lti-integration | -- | -- | Yes | LTI 1.3 platform integration |
lti-ags | -- | -- | Yes | LTI Assignment and Grade Services |
lti-nrps | -- | -- | Yes | LTI Names and Role Provisioning Services |
cmi5-launch | -- | Yes | Yes | cmi5 content launch |
whitelabel | -- | -- | Yes | White-label branding customization |
scorm-export | -- | Yes | Yes | Export content as SCORM packages |
Summary by Plan
- Free: No gated features. Basic H5P creation and playback only.
- Pro (11 features):
pdf-to-h5p,image-hotspot,url-to-h5p,blooms-critique,differentiation,bulk-generation,analytics-dashboard,content-remixer,cmi5-launch,scorm-export - Premium (all 17 features): Everything in Pro, plus
video-to-h5p,lesson-bundle,branching-scenario,ai-recommendation,lti-integration,lti-ags,lti-nrps,whitelabel
Rate Limiting
Rate limits are defined in server/config/security.ts and applied via express-rate-limit. All tiers use a 15-minute sliding window.
| Tier | Max Requests (per 15 min) | Applied To |
|---|---|---|
AUTH | 10 | /login, /refresh |
AI | 50 | AI generation endpoints |
API | 100 | General authenticated API endpoints |
PUBLIC | 200 | Public/unauthenticated endpoints |
When E2E_TEST_CODE is set, all tiers are raised to 10,000 requests per window.
Rate limit responses use standard headers (RateLimit-*) and return:
{
"success": false,
"error": "Too many requests. Please try again later."
}
Usage Tracking
Usage counters are tracked per user in the creatiq_users table. Counters reset automatically on a monthly basis (checked on each user lookup based on month_reset_date).
Tracked Metrics
| Metric | Column | Reset Period |
|---|---|---|
| Contents created | contents_created_this_month | Monthly |
| AI generations | ai_generations_this_month | Monthly |
| Storage used | storage_used (bytes) | Cumulative (no reset) |
Limit Enforcement
Before performing a gated action, the subscription service checks usage via checkLimit(userId, action):
- Content creation: Blocked when
contentsCreatedThisMonth >= plan.contentsPerMonth(unless limit is-1for unlimited). - AI generation: Uses atomic check-and-increment (
UPDATE ... WHERE ai_generations_this_month < limit) to prevent race conditions under concurrent requests. - Storage: Tracked cumulatively. The limit is defined per plan but enforcement is at the application level.
A limit of -1 means unlimited (Premium plan for both contents and AI generations).
Usage Stats API
GET /auth/me returns the current user's usage alongside plan details:
{
"success": true,
"data": {
"user": { "id": "...", "email": "...", "name": "...", "plan": "pro" },
"usage": {
"plan": "pro",
"contentsUsed": 12,
"contentsLimit": 30,
"aiUsed": 45,
"aiLimit": 100,
"storageUsed": 52428800,
"storageLimit": 5368709120
},
"planDetails": {
"id": "pro",
"name": "Pro",
"price": 4,
"limits": {
"contentsPerMonth": 30,
"aiGenerationsPerMonth": 100,
"storageBytes": 5368709120
}
}
}
}
Stripe Integration
Stripe handles subscription billing. It is initialized only when STRIPE_SECRET_KEY is set; otherwise payment features are disabled and a warning is logged.
Required Environment Variables
| Variable | Purpose |
|---|---|
STRIPE_SECRET_KEY | Stripe API authentication |
STRIPE_WEBHOOK_SECRET | Webhook signature verification |
STRIPE_PRO_PRICE_ID | Stripe Price ID for Pro plan ($4/month) |
STRIPE_PREMIUM_PRICE_ID | Stripe Price ID for Premium plan ($8/month) |
API Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /auth/plans | No | List all plans with limits and pricing |
POST | /auth/checkout | Yes | Create a Stripe Checkout session for plan upgrade |
POST | /auth/billing-portal | Yes | Create a Stripe Billing Portal session for subscription management |
POST | /auth/webhook | Stripe signature | Handle Stripe webhook events |
Checkout Flow
- User calls
POST /auth/checkoutwith{ "planId": "pro" }or{ "planId": "premium" }. - The service creates (or reuses) a Stripe Customer tied to the user.
- A Stripe Checkout Session is created in
subscriptionmode with the plan's Price ID. - The checkout session URL is returned. The user is redirected to Stripe.
- On success, Stripe redirects to
NEXT_PUBLIC_APP_URL/workspace?subscription=success. - On cancel, Stripe redirects to
NEXT_PUBLIC_APP_URL/workspace?subscription=cancelled.
Webhook Events Handled
| Event | Action |
|---|---|
checkout.session.completed | Updates user's plan and stores stripe_subscription_id |
customer.subscription.deleted | Downgrades user to free plan and clears subscription ID |
invoice.payment_failed | Logs a warning (no automatic downgrade) |
Webhook idempotency is enforced via Redis: each event ID is stored with a 24-hour TTL (webhook:<event_id>). Duplicate events receive a 200 response with { "duplicate": true }.
Stripe API Version
The Stripe client is initialized with API version 2025-12-15.clover.
JIT Plan Assignment on Login
When a user authenticates via Keycloak, their Creatiq plan is determined by their Keycloak role:
| Keycloak Role | Assigned Plan |
|---|---|
admin | premium |
teacher | pro |
| All others | free |
This assignment only applies when the user record is first created (JIT provisioning). Subsequent Stripe upgrades take precedence.
Database Schema
The creatiq_users table stores subscription and usage data:
| Column | Type | Default | Description |
|---|---|---|---|
id | UUID | gen_random_uuid() | Primary key |
email | VARCHAR(255) | -- | Unique, lowercased |
name | VARCHAR(255) | -- | Display name |
plan | VARCHAR(20) | 'free' | Current plan (free, pro, premium) |
stripe_customer_id | VARCHAR(255) | NULL | Stripe Customer ID |
stripe_subscription_id | VARCHAR(255) | NULL | Active Stripe Subscription ID |
contents_created_this_month | INTEGER | 0 | Monthly content counter |
ai_generations_this_month | INTEGER | 0 | Monthly AI generation counter |
storage_used | BIGINT | 0 | Total storage in bytes |
month_reset_date | DATE | CURRENT_DATE | Date of last monthly counter reset |
external_user_id | VARCHAR(255) | NULL | Keycloak sub claim |
tenant_id | UUID | NULL | Foreign key to tenants(id) |
created_at | TIMESTAMP | CURRENT_TIMESTAMP | Record creation time |
updated_at | TIMESTAMP | CURRENT_TIMESTAMP | Last update time |
Indexes
| Index | Columns | Condition |
|---|---|---|
idx_creatiq_users_email | email | -- |
idx_creatiq_users_stripe_customer | stripe_customer_id | -- |
idx_creatiq_users_tenant_external (unique) | tenant_id, external_user_id | WHERE tenant_id IS NOT NULL AND external_user_id IS NOT NULL |
idx_creatiq_users_external_user_id | external_user_id | WHERE external_user_id IS NOT NULL |