Subscriptions and Entitlements
These read-only endpoints tell your application which plan a user is on, what that plan allows, and how much of the monthly allowance has been used. All three require a bearer token (the signed-in user) and return data for that user only.Endpoints#
| Method | Path | Purpose |
|---|
| GET | /api/subscriptions/me | Effective plan, entitlements, and subscription state |
| GET | /api/subscriptions/limits | What the plan allows. null means unlimited |
| GET | /api/subscriptions/usage | Usage in the current calendar month (UTC) |
Limits and usage are what the backend enforces, so you can use them to show quota UI before a request is rejected.GET /api/subscriptions/me#
{
"plan": "team",
"entitlements": { },
"subscription_status": "active",
"current_period_end": "2026-11-13T00:00:00+00:00",
"last_plan_code": "team",
"last_cycle": "monthly",
"team_licenses": 12,
"team_min_licenses": 10,
"queued_subscription": {
"id": "…",
"plan_code": "team",
"cycle": "annual",
"starts_at": "2026-11-13T00:00:00+00:00",
"ends_at": "2027-11-13T00:00:00+00:00"
}
}
plan is the plan currently in effect. Users without an active paid subscription are on basic.
subscription_status is active, canceled, expired, or null if the user has never subscribed.
queued_subscription appears only when a renewal or plan change is scheduled to start after the current period.
GET /api/subscriptions/limits#
{
"plan": "pro",
"certificates_monthly_limit": 10,
"pdf_monthly_limit": 20,
"youtube_minutes_monthly_limit": null,
"quiz_generation_monthly_limit": 50,
"analytics_erpi": true,
"analytics_export": true,
"organization_create": true,
"organization_invite": true,
"live_monthly_limit_seconds": 36000,
"youtube_upload_enabled": false,
"cloudflare_stream_upload_enabled": false,
"secure_enterprise_streaming": false,
"private_storage_minutes": 0,
"secure_enterprise_streaming_addon_price_idr": 50000,
"secure_enterprise_streaming_addon_price_usd_cents": 300,
"reset_date": "2026-11-01T00:00:00+00:00"
}
(The numbers above are illustrative. Read the real values from the endpoint.)Any *_limit field set to null means unlimited.
reset_date is when monthly counters reset: the first day of the next calendar month, UTC.
GET /api/subscriptions/usage#
{
"month_key": "2026-10",
"chat_messages_count": 120,
"certificates_issued_count": 2,
"pdf_uploads_count": 4,
"quiz_generations_count": 7,
"youtube_imports_count": 35,
"live_seconds_used": 1800
}
youtube_imports_count is measured in minutes. live_seconds_used is in seconds. Compare each counter with the matching field from /limits.When a monthly limit is reached, the quota-protected endpoint responds with 429 and a body like:{
"detail": {
"error": "…",
"message": "Monthly pdf limit reached on the PRO plan.",
"upgrade_required": true,
"quota": "pdf",
"plan": "pro",
"limit": 20,
"used": 20
}
}
Seats and licenses#
The Team plan is sold per license (seat). A subscription has a quantity: the number of licenses purchased.team_licenses in /me is the number of licenses currently held, including a queued renewal. It is the default quantity for the next renewal.
team_min_licenses is the fewest licenses the next renewal can buy: the number of seats in use, and never less than the Team minimum of 10.
Extra seats can be added mid-period. Each addition is recorded on its invoice as seat_addition.
Checkout and seat-purchase requests accept a quantity field.
Entitlement sources#
Some organization-level capabilities, currently Xentree Connect and custom domains, are granted by the plan or manually by Xentree. Each organization records where the grant came from:| Source | Meaning |
|---|
plan | Granted because the organization's administrator is on a plan that includes it. It follows the plan: it is withdrawn when the plan lapses and granted again when it returns |
manual | Granted by Xentree support. It is kept when plan changes occur |
The organization response exposes the resulting flags as connect_entitlement and custom_domain_entitlement. Without the corresponding entitlement, the related Connect and custom-domain endpoints are rejected.AI keys (BYOK)#
AI features run on the learner's own Google Gemini API key. Plans control quotas, not AI provider spend. If a user has not connected a key, AI endpoints respond with HTTP 428 Precondition Required. See Errors and Limits. Modified at 2026-10-13 16:08:30