Plans
The Plans API serves the canonical subscription tier catalog — display names, per-active-user pricing for both billing cycles, hard caps, and included capabilities. It exists so the marketing site (embaylms.com) and any other consumer render pricing from one source instead of hardcoding their own copy. The advertised capability list is drift-guarded in CI against the feature flags the server actually enforces.
GET /api/v1/plans is public — no authentication required — and cacheable
(Cache-Control: max-age=300). CORS is open to the corporate site.
Endpoint
GET /api/v1/plansResponse
{
"data": [
{
"id": "starter",
"displayName": "Starter",
"pricePerActiveUserCents": 400,
"pricePerActiveUserCentsMonthly": 500,
"pricePerActiveUserCentsAnnual": 400,
"seatCap": null,
"courseCap": null,
"capabilities": ["custom_branding", "certificates", "sso_saml"],
"highlights": ["Pay per active user — no user cap", "Unlimited courses", "..."]
}
],
"meta": null,
"error": null
}data always contains the four tiers in order: free, starter, growth,
enterprise.
The Plan Object
| Field | Type | Notes |
|---|---|---|
id | string | Tier id: free, starter, growth, enterprise. Stable — Stripe metadata and internal entitlements key on these values. |
displayName | string | Human-readable tier name |
pricePerActiveUserCents | integer | null | Headline monthly rate (CAD cents) — the annual-cycle effective price. 0 = Free, null = custom (Enterprise). Prefer the explicit per-cycle fields below. |
pricePerActiveUserCentsMonthly | integer | null | Per active-user monthly rate on the monthly billing cycle. null = custom. |
pricePerActiveUserCentsAnnual | integer | null | Per active-user effective monthly rate on the annual cycle (20% off monthly). null = custom. |
seatCap | integer | null | Hard active-user cap. Only Free is capped (5); null = uncapped (pure per-active-user billing). |
courseCap | integer | null | Hard active-course cap, enforced at course publish. Only Free is capped (3); null = unlimited. |
storageCapMb | integer | null | Storage cap in MB, enforced at upload (Free 1024, Starter 10240, Growth 51200). null = unlimited. |
capabilities | string[] | Flag-gated capabilities included in the tier — guaranteed equal to what the server enforces. |
highlights | string[] | Display-only marketing bullets (English). Not machine-readable entitlements — use capabilities and the cap fields for logic. |
Pricing model
Paid tiers are billed per active user per month (an active user is a user
who logged in during the calendar month). Annual billing applies a 20% discount
to the monthly rate — e.g. Starter is $5.00/user/mo monthly or $4.00/user/mo
billed annually. Enterprise pricing is custom (sales-assisted); all its price
fields are null.
Compatibility
The response shape only ever changes additively — existing fields are never
renamed or removed, so cached consumers keep working. New fields (like the
per-cycle prices and courseCap) may appear over time.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| CORS error in the browser | The Access-Control-Allow-Origin is pinned to the corporate site URL. Server-side fetches are unaffected; browser consumers must be served from the allowed origin. |
| Stale prices after a catalog change | The response is CDN-cached for 5 minutes (max-age=300). Wait out the TTL or bust the cache with a query string while testing. |
| A capability shown here seems unavailable in-app | capabilities reflects the tier’s entitlement, not a specific tenant’s state — a tenant mid-downgrade or on an expired trial may differ. Check the tenant’s billing portal. |