📘 Subscription & Usage System – Complete Documentation
1. Overview
The IE Research Platform offers a freemium subscription model with two tiers:
| Tier | Plan ID | Description |
|---|---|---|
| Free | FREE |
Basic features for individual researchers |
| Professional Monthly | PRO_MONTHLY |
Advanced features, billed monthly |
| Professional Yearly | PRO_YEARLY |
Advanced features, billed annually (2 months free) |
All plans include monthly usage quotas that reset on the 1st of each month.
2. Subscription Plans – Detailed Comparison
| Feature | Free | Pro Monthly | Pro Yearly |
|---|---|---|---|
| Monthly Price | $0 | $29.99 | $24.99 (billed $299.99/year) |
| Articles per month | 5 | 100 | 100 |
| Storage space | 100 MB | 5 GB | 5 GB |
| API calls per month | 100 | 5,000 | 5,000 |
| AI generations per month | 10 | 500 | 500 |
| Team members | 1 | 10 | 10 |
| AI‑powered search | ❌ | ✅ | ✅ |
| Advanced analytics | ❌ | ✅ | ✅ |
| API access | ❌ | ✅ | ✅ |
| Priority support | ❌ | ✅ | ✅ |
| Collaboration tools | ❌ | ✅ | ✅ |
| Export citations (BibTeX, RIS, etc.) | ❌ | ✅ | ✅ |
| Free trial | – | 14 days | 14 days |
| Auto‑renewal | – | Optional | Optional |
3. Usage Tracking & Limits
Each user’s monthly usage is tracked per calendar month (UTC). Counters reset automatically on the 1st day of each month.
3.1 Tracked Metrics
| Metric | Description | Free Limit | Pro Limit |
|---|---|---|---|
articles_created |
Number of published articles | 5 | 100 |
storage_used_mb |
Total uploaded files size | 100 MB | 5,120 MB (5 GB) |
api_calls |
REST API requests | 100 | 5,000 |
ai_generations |
AI‑powered features (summaries, grammar check, etc.) | 10 | 500 |
team_members |
Number of users in collaborative projects | 1 | 10 |
3.2 Usage Enforcement
- When a user attempts to create a new article, the system checks
articles_created < limit. If exceeded, the request is rejected with429 Too Many Requests. - File uploads check
storage_used_mb + file_size_mb ≤ limit. - Each AI endpoint call consumes one AI generation token.
- API calls are counted per request (excluding public/static endpoints).
3.3 Usage Reset & Pro‑rating
- Counters reset to zero on the 1st day of every month at 00:00 UTC.
- When a user upgrades mid‑month, the remaining quota for that month is not prorated – they immediately receive the full Pro limit. However, downgrading to Free mid‑month does not revoke already‑used quota – the user will be blocked from creating new content if they exceed the Free limit.
4. Subscription API Endpoints
Base URL: https://yourdomain.com/api/subscription
4.1 Get Available Plans
GET /api/subscription/plans
Response example:
{
"success": true,
"plans": [
{
"id": "FREE",
"name": "Free",
"priceMonthly": 0.0,
"priceYearly": 0.0,
"currency": "USD",
"features": ["Up to 5 articles", "100 MB storage", "100 API calls per month", ...],
"limits": { "articles": 5, "storageMB": 100, "apiCalls": 100, "aiGenerations": 10, "teamMembers": 1 },
"popular": false,
"recommended": false,
"trialDays": 0
},
{
"id": "PRO_MONTHLY",
"name": "Professional",
"priceMonthly": 29.99,
"priceYearly": 299.99,
"description": "Advanced features for professional researchers",
"features": ["Up to 100 articles", "5 GB storage", "5000 API calls", "500 AI generations", ...],
"limits": { "articles": 100, "storageMB": 5120, "apiCalls": 5000, "aiGenerations": 500, "teamMembers": 10 },
"popular": true,
"trialDays": 14
},
{
"id": "PRO_YEARLY",
"name": "Professional (Yearly)",
"priceMonthly": 24.99,
"priceYearly": 299.99,
"recommended": true,
"trialDays": 14
}
]
}
4.2 Get Single Plan
GET /api/subscription/plan/{planId}
Path parameters: planId = FREE, PRO_MONTHLY, or PRO_YEARLY
4.3 Get Current User’s Subscription
GET /api/subscription/me
Headers: Authorization: Bearer <token>
Response example:
{
"success": true,
"subscription": {
"userId": 123,
"username": "researcher1",
"plan": "PRO_MONTHLY",
"status": "active",
"startDate": "2025-01-15T10:00:00",
"endDate": "2025-02-15T10:00:00",
"autoRenew": true,
"cancelled": false,
"isTrial": false,
"trialDaysRemaining": 0,
"daysRemaining": 28,
"articleLimit": 100,
"storageLimitMB": 5120,
"apiCallsLimit": 5000,
"aiGenerationsLimit": 500,
"teamMembersLimit": 10,
"articlesUsed": 12,
"storageUsedMB": 245,
"apiCallsUsed": 387,
"aiGenerationsUsed": 23,
"teamMembersUsed": 3,
"features": {
"aiSearch": true,
"advancedAnalytics": true,
"apiAccess": true,
"prioritySupport": true,
"collaboration": true,
"exportCitations": true
},
"paymentMethod": "credit_card",
"paymentProvider": "stripe",
"price": 29.99,
"currency": "USD",
"lastInvoiceId": "INV-1737123456789-ABC12345",
"lastInvoiceDate": "2025-01-15T10:00:00",
"lastInvoiceAmount": 29.99
}
}
4.4 Subscribe to a Plan
POST /api/subscription/subscribe
Headers: Authorization: Bearer <token>, Content-Type: application/json
Request body:
{
"planId": "PRO_MONTHLY",
"paymentMethod": "credit_card",
"paymentToken": "pm_xxx", // from Stripe/PayPal
"couponCode": "WELCOME10", // optional
"autoRenew": true
}
Response: same as GET /api/subscription/me
4.5 Upgrade to Pro (convenience)
POST /api/subscription/upgrade?yearly=false
Query parameter: yearly – false for monthly, true for yearly.
Response: same as GET /api/subscription/me
4.6 Cancel Subscription (at period end)
POST /api/subscription/cancel
Effect: sets cancelled = true and autoRenew = false. Access continues until endDate.
Response:
{
"success": true,
"message": "Subscription cancelled. You will have access until the end of the billing period."
}
4.7 Downgrade to Free Immediately
POST /api/subscription/downgrade
Effect: immediately sets plan to FREE and removes future expiry. Warning: any over‑usage of Free limits will block further actions.
4.8 Get Current Usage (without consuming)
GET /api/subscription/usage
Response:
{
"success": true,
"usage": {
"remainingGenerations": 477,
"canCreateArticle": 1
}
}
4.9 Consume an AI Generation
POST /api/subscription/consume
Effect: increments ai_generations counter. Returns 429 if limit reached.
Response:
{
"success": true,
"remaining": 477
}
4.10 List Invoices (paginated)
GET /api/subscription/invoices?page=0&limit=20
Response:
{
"success": true,
"invoices": [
{
"invoiceNumber": "INV-...",
"amount": 29.99,
"currency": "USD",
"status": "paid",
"issuedAt": "2025-01-15T10:00:00",
"pdfUrl": "/api/subscription/invoices/INV-.../pdf"
}
]
}
4.11 Admin Endpoints
| Endpoint | Method | Description | Role |
|---|---|---|---|
/api/subscription/admin/all?page=0&limit=20 |
GET | List all users’ subscriptions (with usage) | ADMIN |
/api/subscription/admin/stats |
GET | Aggregated subscription statistics | ADMIN |
Admin stats response:
{
"success": true,
"statistics": {
"totalUsers": 1250,
"freeUsers": 980,
"proMonthly": 200,
"proYearly": 70,
"activePro": 270,
"conversionRate": 21.6,
"monthlyRecurringRevenue": 12998.50
}
}
5. Webhook Integration (Payment Providers)
To handle subscription events (payment success, cancellation, renewal), configure a webhook endpoint:
POST /api/subscription/webhook/{provider}
Supported providers: stripe, paypal
Example Stripe webhook payload handling:
{
"type": "invoice.payment_succeeded",
"data": {
"object": {
"customer": "cus_xxx",
"subscription": "sub_xxx",
"amount_paid": 2999,
"currency": "usd"
}
}
}
The service will update the user’s subscription expiry date and create a new invoice.
6. Usage Middleware / Interceptors
To enforce limits, the following filters/interceptors should be applied:
| Limitation | Interceptor |
|---|---|
| Article creation | ArticleService.create() checks articles_used < limit before saving |
| File upload | FileUploadService checks storage limit before writing file |
| API calls | A RateLimitFilter or UsageInterceptor increments api_calls per authenticated request |
| AI features | Each AI endpoint calls SubscriptionService.consumeGeneration() |
| Team member invitations | ProjectService.addMember() checks team member limit |
7. Database Schema (Summary)
-- Users table (extended)
ALTER TABLE users ADD COLUMN subscription_plan VARCHAR(20) DEFAULT 'FREE';
ALTER TABLE users ADD COLUMN subscription_expiry TIMESTAMP;
ALTER TABLE users ADD COLUMN subscription_start TIMESTAMP;
ALTER TABLE users ADD COLUMN subscription_cancelled BOOLEAN DEFAULT 0;
ALTER TABLE users ADD COLUMN subscription_auto_renew BOOLEAN DEFAULT 1;
-- Monthly usage
CREATE TABLE subscription_usage (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
year_month VARCHAR(7) NOT NULL,
articles_created INTEGER DEFAULT 0,
api_calls INTEGER DEFAULT 0,
storage_used_mb INTEGER DEFAULT 0,
ai_generations INTEGER DEFAULT 0,
UNIQUE(user_id, year_month)
);
-- Invoices
CREATE TABLE invoices (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
invoice_number VARCHAR(50) UNIQUE NOT NULL,
plan VARCHAR(20) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
currency VARCHAR(3) DEFAULT 'USD',
status VARCHAR(20) DEFAULT 'pending',
payment_method VARCHAR(50),
payment_provider VARCHAR(50),
payment_provider_id VARCHAR(100),
issued_at TIMESTAMP,
paid_at TIMESTAMP
);
8. Error Codes
| HTTP Status | Code | Description |
|---|---|---|
| 429 | LIMIT_REACHED |
Monthly quota exceeded (articles, storage, API calls, or AI generations) |
| 402 | PAYMENT_REQUIRED |
Subscription expired or missing payment |
| 400 | SUBSCRIPTION_ERROR |
Invalid plan ID or coupon code |
| 403 | FORBIDDEN |
Admin endpoint accessed by non‑admin |
9. Example Frontend Integration
Show current plan and usage bar
fetch('/api/subscription/me', { headers: { 'Authorization': `Bearer ${token}` } })
.then(res => res.json())
.then(data => {
const sub = data.subscription;
document.getElementById('planName').innerText = sub.plan;
document.getElementById('articlesUsed').innerText = `${sub.articlesUsed}/${sub.articleLimit}`;
const percent = (sub.articlesUsed / sub.articleLimit) * 100;
document.getElementById('usageFill').style.width = `${percent}%`;
});
Upgrade button
document.getElementById('upgradeBtn').addEventListener('click', async () => {
const res = await fetch('/api/subscription/upgrade?yearly=false', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` }
});
if (res.ok) location.reload();
else alert('Upgrade failed');
});
10. Automated Renewal & Expiry Check
A scheduled job (cron 0 0 0 * * *) runs daily to:
- Expire subscriptions where
endDate < now()andautoRenew == false - For
autoRenew == true, attempt to charge the saved payment method and extendendDateby one billing period - Send expiry notifications 7, 3, and 1 day before expiry
Xet Storage Details
- Size:
- 12.1 kB
- Xet hash:
- bd73d9d3e40d2b50aa10a04aa2bd94d85867b22891f4df1026e41453738d307c
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.